GPU 컴퓨팅 (GPGPU)
Linux GPU 컴퓨팅(GPGPU) 프레임워크와 런타임을 심층 분석합니다. GPU 컴퓨트 개요, CUDA/NVIDIA 아키텍처, OpenCL 크로스 플랫폼 컴퓨트, Vulkan Compute 파이프라인, ROCm/HIP AMD GPU 컴퓨트, Intel oneAPI/Level Zero 플랫폼의 구조와 Linux 커널 드라이버 연동을 다룹니다.
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
SVM (Shared Virtual Memory)
SVM은 CPU와 GPU가 동일한 가상 주소 공간을 공유하는 기술입니다.
malloc()으로 할당한 메모리를 GPU가 동일한 포인터로 접근할 수 있습니다.
/* 커널 SVM 지원 (amdgpu/KFD) */
#include <linux/hmm.h>
/* GPU 페이지 폴트 핸들러 */
/* 1. GPU가 매핑되지 않은 주소 접근 → 인터럽트 */
/* 2. 커널이 CPU 페이지 테이블에서 물리 주소 조회 */
/* (페이지 없으면 CPU 페이지 폴트도 처리) */
/* 3. GPU 페이지 테이블에 매핑 추가 */
/* 4. GPU 작업 재개 */
/dev/dri/renderD128은 DRM Master 없이 접근 가능하므로, 일반 사용자도 GPU 컴퓨트를 사용할 수 있습니다.
그러나 GPU 가상 메모리 격리가 제대로 구현되어야 다른 프로세스의 GPU 데이터가 유출되지 않습니다.
per-process GPU page table과 command 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 GPU | DRM 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)로 연결된 두 드라이버로 분리하는 설계를 권합니다.
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를 활용합니다.
NVIDIA GPU 아키텍처 진화
NVIDIA GPU 아키텍처는 2006년 Tesla(CUDA 최초 지원)부터 시작하여 세대마다 SM(Streaming Multiprocessor) 구조,
메모리 계층, 인터커넥트, 전용 하드웨어 유닛을 혁신해 왔습니다.
각 세대의 Compute Capability는 지원하는 CUDA 기능 집합을 결정하며,
nvcc의 -arch=sm_XX 옵션으로 대상 아키텍처를 지정합니다.
| 아키텍처 | CC | 대표 GPU | CUDA 코어 | Tensor Core | 메모리 | NVLink | 핵심 혁신 |
|---|---|---|---|---|---|---|---|
| Tesla | 1.0 | G80 | 128 | — | 768MB GDDR3 | — | CUDA 최초 도입 |
| Fermi | 2.0 | GF100 | 512 | — | 6GB GDDR5 | — | L1/L2 캐시, ECC |
| Kepler | 3.5 | GK110 | 2880 | — | 12GB GDDR5 | — | Dynamic Parallelism |
| Maxwell | 5.2 | GM200 | 3072 | — | 12GB GDDR5 | — | 에너지 효율 2× |
| Pascal | 6.0 | GP100 | 3840 | — | 16GB HBM2 | 1.0 (160GB/s) | NVLink, FP16 |
| Volta | 7.0 | V100 | 5120 | 640 (1세대) | 32GB HBM2 | 2.0 (300GB/s) | Tensor Core 도입 |
| Turing | 7.5 | T4 | 2560 | 320 (2세대) | 16GB GDDR6 | — | RT Core, INT8/INT4 |
| Ampere | 8.0 | A100 | 6912 | 432 (3세대) | 80GB HBM2e | 3.0 (600GB/s) | TF32, MIG, 희소성 2:4 |
| Hopper | 9.0 | H100 | 16896 | 528 (4세대) | 80GB HBM3 | 4.0 (900GB/s) | FP8, TMA, DPX |
| Blackwell | 10.0 | B200 | 18432 | 576 (5세대) | 192GB HBM3e | 5.0 (1800GB/s) | FP4, 2세대 TMA |
-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에서 실행 불가합니다.
NVIDIA Linux 드라이버 스택
NVIDIA GPU를 CUDA로 활용하려면 커널 모듈 + 사용자 라이브러리가 함께 설치되어야 합니다. 아래 다이어그램은 CUDA 애플리케이션에서 GPU 하드웨어까지의 전체 소프트웨어 스택을 보여줍니다.
| 디바이스 노드 | 제공 모듈 | 용도 |
|---|---|---|
/dev/nvidia0..N | nvidia.ko | GPU별 컨트롤 채널 (컴퓨트, 메모리 할당) |
/dev/nvidiactl | nvidia.ko | 전역 컨트롤 (디바이스 열거, 초기화) |
/dev/nvidia-uvm | nvidia-uvm.ko | Unified Virtual Memory 관리 |
/dev/nvidia-uvm-tools | nvidia-uvm.ko | UVM 프로파일링 / 디버깅 |
/dev/nvidia-modeset | nvidia-modeset.ko | 디스플레이 모드 설정 |
/dev/dri/card* | nvidia-drm.ko | DRM primary 노드 (Wayland/X11 연동) |
/dev/dri/renderD* | nvidia-drm.ko | DRM 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 컴파일이나 멀티 컨텍스트 관리에 필수적입니다.
| 항목 | Runtime API (cudart) | Driver API (cuda) |
|---|---|---|
| 헤더 | cuda_runtime.h | cuda.h |
| 라이브러리 | libcudart.so | libcuda.so (드라이버와 함께 설치) |
| 초기화 | 암묵적 (첫 API 호출 시) | 명시적 (cuInit(0)) |
| 컨텍스트 | 기본 컨텍스트 자동 생성 | 수동 생성/파괴 (cuCtxCreate) |
| 커널 실행 | <<<...>>> 구문 | cuLaunchKernel() |
| PTX JIT 로드 | 불가 | cuModuleLoadDataEx() |
| 디바이스 관리 | cudaSetDevice() | cuDeviceGet() + cuCtxCreate() |
| 혼용 | 가능 — 동일 프로세스에서 두 API를 함께 사용할 수 있음 | |
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 정보 상세 출력
| 방법 | 장점 | 단점 | 적합한 환경 |
|---|---|---|---|
| NVIDIA 저장소 (cuda-keyring) | 자동 업데이트, 의존성 해결 | 시스템 전역 설치 | 프로덕션, CI/CD |
| runfile 직접 설치 | 설치 경로 지정, 다중 버전 공존 | 의존성 수동 관리 | 개발, HPC 클러스터 |
| Conda (conda-forge) | 가상환경 격리, 크로스 플랫폼 | 드라이버는 별도 설치 | 데이터 과학, ML |
| 컨테이너 (nvidia/cuda) | 완전 격리, 재현성 | nvidia-container-toolkit 필요 | 클라우드, K8s |
/usr/local/cuda는 심볼릭 링크이며
update-alternatives --config cuda로 활성 버전을 전환할 수 있습니다.
ls /usr/local/cuda-*/로 설치된 모든 버전을 확인하고,
프로젝트별로 PATH와 LD_LIBRARY_PATH를 조정하세요.
CUDA_HOME 환경 변수를 설정하면 CMake의 FindCUDA 모듈이 자동으로 인식합니다.
CUDA 프로그래밍 모델
CUDA는 SIMT(Single Instruction Multiple Thread) 실행 모델을 사용합니다.
프로그래머는 __global__ 함수(커널)를 정의하고, 호스트에서 <<<gridDim, blockDim>>> 구문으로 수천~수백만 스레드(Thread)를 동시에 실행합니다.
| CUDA | OpenCL | 설명 |
|---|---|---|
| Grid | NDRange | 전체 문제 공간 (커널 1회 실행) |
| Block | Work-group | SM에 매핑, 공유 메모리/배리어 범위 |
| Thread | Work-item | 개별 실행 단위 |
| Warp (32) | Sub-group | SIMT 동시 실행 단위, 하드웨어 결정 |
__shared__ | __local | 블록/그룹 내 공유 메모리 |
__syncthreads() | barrier() | 블록/그룹 내 동기화 |
threadIdx.x | get_local_id(0) | 블록/그룹 내 인덱스 |
blockIdx.x | get_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 커널 실행의 핵심 개념을 정리하면 다음과 같습니다:
| 개념 | 설명 | 제약 조건 |
|---|---|---|
__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)가 발생하여 각 경로를 순차적으로 실행해야 합니다.
분기 다이버전스를 최소화하는 것은 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;
/* ... 실제 연산 ... */
}
__syncwarp(mask)로 명시적 재수렴을 보장하고,
__shfl_sync(mask, ...)에서 항상 유효한 마스크를 전달하세요.
CUDA 스트림과 비동기 실행
CUDA 스트림(Stream)은 순서가 보장되는 GPU 명령 큐입니다. 서로 다른 스트림의 명령은 하드웨어가 허용하는 한 동시에 실행될 수 있으며, 이를 통해 커널 실행, 메모리 전송, 호스트 연산을 오버랩하여 GPU 파이프라인 활용률을 극대화합니다.
/* 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 = 커널 실행 시간 (밀리초) */
| 메커니즘 | 범위 | 동기화 | 사용 사례 |
|---|---|---|---|
| 기본 스트림 (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);
cudaGraphExecUpdate()로
재인스턴스화 없이 갱신할 수 있습니다.
CUDA 메모리 계층
GPU 성능 최적화의 핵심은 메모리 계층을 이해하고 활용하는 것입니다. CUDA 메모리는 크게 레지스터 → 공유 메모리(Shared) → L1/L2 캐시 → 글로벌 메모리(VRAM) 순으로 용량이 커지고 지연(Latency) 시간이 증가합니다.
| 메모리 | 선언 | 범위 | 수명 | 지연 | 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;
}
nvidia-smi dmon의 mem_util이 낮다면
접근 패턴 최적화를 먼저 확인하세요.
| 접근 패턴 | 트랜잭션 수 | 대역폭 활용 | 설명 |
|---|---|---|---|
| 연속 정렬 (Aligned Coalesced) | 1× (128B) | 100% | T0→addr[0], T1→addr[1], ..., T31→addr[31] |
| 연속 비정렬 (Misaligned) | 2× | ~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() 없이도 양쪽에서 데이터에 접근할 수 있습니다.
| 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);
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)을 달성합니다.
| 정밀도 | 입력→출력 | Volta | Ampere | Hopper | Blackwell | 주요 사용처 |
|---|---|---|---|---|---|---|
| FP16 | FP16→FP32 | 125 T | 312 T | 989 T | 2250 T | 딥러닝 학습/추론 |
| BF16 | BF16→FP32 | — | 312 T | 989 T | 2250 T | LLM 학습 (높은 동적 범위) |
| TF32 | TF32→FP32 | — | 156 T | 495 T | 1125 T | FP32 드롭인 대체 (cuBLAS 자동) |
| FP8 (E4M3) | FP8→FP16/32 | — | — | 1979 T | 4500 T | LLM 추론, 양자화 학습 |
| FP4 | FP4→FP16/32 | — | — | — | 9000 T | 초저정밀도 추론 |
| INT8 | INT8→INT32 | — | 624 T | 1979 T | 4500 T | INT8 양자화 추론 |
| 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);
}
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대 제약 요소는 레지스터 사용량, 공유 메모리 사용량, 블록당 스레드 수입니다.
/* 오큐펀시 최적화 — 최적 블록 크기 자동 결정 */
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 */
}
ncu)의 "Occupancy" 섹션에서 실측 오큐펀시와
병목(Bottleneck) 요인(레지스터/공유메모리/블록 수)을 확인하고, 성능 프로파일링 결과에 기반하여 조정하세요.
NVIDIA 커널 모듈 상세
NVIDIA 독점 드라이버는 5개의 커널 모듈로 구성됩니다. 각 모듈은 역할이 명확히 분리되어 있으며,
sysfs와 procfs를 통해 런타임 상태를 조회하거나 파라미터를 변경할 수 있습니다.
| 모듈 | 역할 | 주요 인터페이스 |
|---|---|---|
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 (절전)
dkms status nvidia로 현재 빌드 상태를 확인하세요.
| 파라미터 | 기본값 | 설명 | 조정 시나리오 |
|---|---|---|---|
NVreg_EnablePCIeGen3 | 1 | PCIe Gen3 모드 활성화 | 호환성 문제 시 0으로 비활성화 |
NVreg_MemoryPoolSize | 256 | UVM 내부 메모리 풀 (MB) | 대규모 UVM 사용 시 증가 |
NVreg_PreserveVideoMemoryAllocations | 0 | 서스펜드 시 VRAM 보존 | 절전/하이버네이트 사용 시 1 |
NVreg_RegistryDwords | — | 레지스터 레벨 설정 주입 | NVIDIA 지원팀 지시에 따라 |
NVreg_EnableGpuFirmware | 0 | GSP 펌웨어 강제 활성화 | 오픈 커널 모듈 전환 시 1 |
NVreg_OpenRmEnableUnsupportedGpus | 0 | 미지원 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-smi -pm 1로 설정하는 Persistence Mode는 GPU 초기화 상태를 유지하지만,
nvidia-persistenced 데몬이 더 안정적입니다. 데몬은 GPU당 최소 컨텍스트를 유지하여
마지막 사용자 프로세스 종료 후에도 드라이버가 언로드되지 않게 합니다.
HPC 클러스터에서는 두 방법 모두 활성화하는 것이 일반적입니다.
GPUDirect / RDMA
GPUDirect 기술은 GPU 메모리와 외부 디바이스(다른 GPU, NIC, NVMe) 간의 직접 DMA 경로를 제공하여 CPU 메모리 복사를 제거합니다. HPC와 대규모 AI 학습에서 노드 간 통신 병목을 해소하는 핵심 기술입니다.
| 기술 | 경로 | 커널 모듈 | 대역폭 (예시) |
|---|---|---|---|
| GPUDirect P2P | GPU↔GPU (동일 노드, PCIe) | nvidia.ko | PCIe 4.0: ~32 GB/s |
| NVLink | GPU↔GPU (전용 인터커넥트) | nvidia.ko | NVLink 4.0: 900 GB/s (H100) |
| GPUDirect RDMA | GPU↔NIC (CPU 바이패스) | nvidia-peermem.ko | IB HDR: ~25 GB/s |
| GPUDirect Storage | GPU↔NVMe (CPU 바이패스) | nvidia-fs.ko | PCIe: ~7 GB/s |
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 등 사용 가능한 모든 인터커넥트를 자동으로 감지하여 최적의 통신 토폴로지를 구성합니다.
| 연산 | 입력→출력 | 통신량 | 대표 사용 사례 |
|---|---|---|---|
ncclAllReduce | 각 GPU 텐서 → 합산 결과 전체 복제 | 2(N-1)/N × size | 데이터 병렬 그래디언트 동기화 |
ncclAllGather | 각 GPU 조각 → 전체 텐서 복제 | (N-1)/N × total | Tensor Parallelism 출력 수집 |
ncclReduceScatter | 합산 + 분산 | (N-1)/N × size | ZeRO Stage 2/3 옵티마이저 |
ncclBroadcast | 루트 → 전체 | size | 모델 가중치 초기 배포 |
ncclReduce | 전체 → 루트 합산 | (N-1) × size | 메트릭 수집, 체크포인팅 |
ncclSend/Recv | 점대점 | size | Pipeline 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_ALGO=Ring 또는 Tree로 강제 지정할 수 있으나, 대부분의 경우
자동 선택이 최적입니다. NCCL_DEBUG=INFO로 선택된 알고리즘과 대역폭을 확인하세요.
NVIDIA 오픈 GPU 커널 모듈
2022년 5월, NVIDIA는 open-gpu-kernel-modules 저장소를 통해 GPU 커널 드라이버의 소스 코드를 공개했습니다(GPL-2.0 / MIT 듀얼 라이선스). Turing(GTX 16xx) 이후 아키텍처에서 사용할 수 있으며, 장기적으로 독점 모듈을 대체할 계획입니다.
| 항목 | 오픈 커널 모듈 | 독점 커널 모듈 | nouveau (메인라인) |
|---|---|---|---|
| 라이선스 | GPL-2.0 / MIT | 독점 | GPL-2.0 |
| 소스 공개 | 전체 (커널 부분) | 비공개 | 전체 (리버스 엔지니어링) |
| 지원 GPU | Turing 이후 (530+) | Kepler 이후 | Tesla~Ampere (제한적) |
| CUDA 지원 | 완전 (독점 libcuda.so 필요) | 완전 | 제한적 (Volta+ GSP-RM 기반) |
| 메인라인 포함 | 아니오 (out-of-tree) | 아니오 | 예 |
| GSP 펌웨어 | 필수 (GPU System Processor) | 내장 | 가능 (Turing+) |
| 버그 리포트 | GitHub Issues | NVIDIA 포럼 | freedesktop GitLab |
NVIDIA 컨테이너 / 가상화(Virtualization)
클라우드 및 AI 인프라에서 GPU를 컨테이너·가상 머신에 안전하게 공유하려면 별도 런타임이 필요합니다. NVIDIA는 Container Toolkit, MIG, vGPU, K8s Device Plugin을 통해 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
| 프로파일 | SM 수 | 메모리 | L2 캐시 | 최대 인스턴스 수 | 사용 사례 |
|---|---|---|---|---|---|
| 1g.10gb | 14 | 10 GB | 5 MB | 7 | 소형 추론, 개발/테스트 |
| 1g.20gb | 14 | 20 GB | 10 MB | 4 | 메모리 집약 추론 |
| 2g.20gb | 28 | 20 GB | 10 MB | 3 | 중형 학습/추론 |
| 3g.40gb | 42 | 40 GB | 20 MB | 2 | 대형 모델 학습 |
| 4g.40gb | 56 | 40 GB | 20 MB | 1 | 대형 학습 (단독) |
| 7g.80gb | 108 | 80 GB | 40 MB | 1 | 전체 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-cli가 GPU 디바이스 노드(/dev/nvidia*)와
드라이버 라이브러리(libnvidia-*.so)를 컨테이너 네임스페이스에 바인드 마운트(Bind Mount)합니다.
컨테이너 이미지에는 CUDA 런타임만 포함하면 되며, 드라이버 버전은 호스트에서 자동으로 주입됩니다.
이를 통해 동일 이미지가 다양한 드라이버 버전의 호스트에서 동작합니다.
CUDA 컴파일 파이프라인
CUDA 소스(.cu)는 nvcc 컴파일러 드라이버를 통해 여러 단계를 거칩니다.
호스트 코드는 gcc/clang에 위임되고, 디바이스 코드는 PTX(가상 ISA) → SASS(실제 기계어(Machine Code))로 변환됩니다.
| 단계 | 입력 | 출력 | 도구 | 설명 |
|---|---|---|---|---|
| 1. 전처리 | .cu | .cu.cpp.ii / .cu.gpu | nvcc (cudafe++) | 호스트/디바이스 코드 분리 |
| 2. PTX 컴파일 | 디바이스 코드 | .ptx | cicc | 가상 ISA (중간 표현) |
| 3. 어셈블 | .ptx | .cubin (SASS) | ptxas | 대상 SM 아키텍처용 기계어 |
| 4. Fatbinary | .ptx + .cubin | .fatbin | fatbinary | 다중 아키텍처 번들 |
| 5. 호스트 컴파일 | 호스트 코드 + .fatbin | .o | gcc / clang | fatbin을 ELF에 임베드 |
| 6. 링크 | .o + libcudart | 실행 파일 | ld | CUDA 런타임 라이브러리 링크 |
# 기본 컴파일 (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
~/.nv/ComputeCache에 캐시됩니다.
# 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/ 인터페이스를 활용합니다.
| 도구 | 용도 | 핵심 기능 |
|---|---|---|
nvidia-smi | GPU 모니터링 | 온도, 전력, 메모리, 프로세스, MIG, 클럭 |
nvtop | 실시간(Real-time) 모니터링 (htop 스타일) | GPU/메모리 사용률 그래프, 프로세스 목록 |
nsys (Nsight Systems) | 시스템 프로파일링 | 타임라인 뷰, CPU-GPU 상호작용, CUDA API 추적 |
ncu (Nsight Compute) | 커널 프로파일링 | 오큐펀시, 메모리 대역폭, 워프 스톨 분석 |
cuda-gdb | GPU 디버거 | 커널 내 브레이크포인트, 워프/스레드 단위 검사 |
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 ← 활성 커널 정보
| 메트릭 | 의미 | 최적 범위 | 낮을 때 원인 |
|---|---|---|---|
| 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);
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);
| 에러 코드 | 원인 | 해결 방법 |
|---|---|---|
cudaErrorMemoryAllocation | GPU 메모리 부족 | 할당 크기 줄이기, 메모리 풀 사용, nvidia-smi 확인 |
cudaErrorInvalidConfiguration | 잘못된 블록/그리드 크기 | 블록 크기 ≤ 1024, CC별 제약 확인 |
cudaErrorIllegalAddress | 잘못된 메모리 접근 (SEGFAULT) | compute-sanitizer --tool memcheck |
cudaErrorLaunchTimeout | 커널 실행 시간 초과 (TDR) | 디스플레이 GPU에서 긴 커널 회피, TDR 타임아웃 증가 |
cudaErrorNoKernelImageForDevice | 현재 GPU CC 미지원 바이너리 | 적절한 -arch=sm_XX로 재컴파일 |
cudaErrorAssert | 커널 내 assert() 실패 | 디바이스 코드 로직 디버깅 |
cudaErrorECCUncorrectable | GPU 메모리 ECC 오류 (하드웨어) | nvidia-smi --query-gpu=ecc.errors.uncorrected.total |
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 대응 | 설명 |
|---|---|---|---|
| cuBLAS | 선형대수 | rocBLAS | GEMM, 행렬 분해, Tensor Core 활용 |
| cuDNN | 딥러닝 | MIOpen | Conv, RNN, Attention, BN 등 DNN 프리미티브 |
| cuFFT | FFT | rocFFT | 1D/2D/3D FFT, 배치 처리 |
| cuSPARSE | 희소 행렬 | rocSPARSE | SpMV, SpMM, 희소 행렬 연산 |
| cuRAND | 난수 생성 | rocRAND | 의사/준난수, 병렬 RNG 스트림 |
| NCCL | 집합 통신 | RCCL | AllReduce, AllGather, 다중 GPU/노드 |
| TensorRT | 추론 최적화 | — | 그래프 최적화, INT8/FP8 양자화, 레이어 퓨전 |
| Thrust | 병렬 알고리즘 | rocThrust | sort, reduce, scan (C++ STL 스타일) |
| cuDSS | 직접 희소 솔버 | rocSOLVER | LU, Cholesky, QR 분해 (희소) |
| CUTLASS | GEMM 템플릿 | composable_kernel | Tensor 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 입력 시) */
hipify-perl / hipify-clang 도구는
CUDA 소스를 HIP 코드로 자동 변환합니다. cudaMalloc → hipMalloc,
cublasSgemm → rocblas_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);
| 알고리즘 | 워크스페이스 | 속도 | 정밀도 | 사용 시나리오 |
|---|---|---|---|---|
| IMPLICIT_GEMM | 0 | 기본 | 정확 | 메모리 제약 환경 |
| 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 백엔드 | 커널 생성 | 분산 학습 | 혼합 정밀도 |
|---|---|---|---|---|
| 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 컴파일
torch.cuda.caching_allocator로 GPU 메모리를 풀링합니다.
nvidia-smi에 표시되는 메모리 사용량은 실제 텐서 크기보다 클 수 있습니다.
torch.cuda.memory_summary()로 실제 할당/캐시 상태를 확인하세요.
OOM 발생 시 torch.cuda.empty_cache()로 캐시를 반환하거나,
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True로 메모리 단편화(Fragmentation)를 줄일 수 있습니다.
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 메모리 계층 */
/*
* 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
| 항목 | 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 순서를 따릅니다. 아래 다이어그램은 완전한 호스트 측 워크플로우를 보여 줍니다.
다음은 위 워크플로우를 완전하게 구현한 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;
}
| 코드 | 상수명 | 원인 | 해결 방법 |
|---|---|---|---|
| 0 | CL_SUCCESS | 정상 | — |
| -1 | CL_DEVICE_NOT_FOUND | 지정 타입의 디바이스 없음 | CL_DEVICE_TYPE_ALL로 재시도 |
| -5 | CL_OUT_OF_RESOURCES | 디바이스 메모리 부족 | 버퍼 크기 축소 또는 분할 |
| -6 | CL_OUT_OF_HOST_MEMORY | 호스트 메모리 부족 | 호스트 할당량 확인 |
| -11 | CL_BUILD_PROGRAM_FAILURE | 커널 컴파일 실패 | clGetProgramBuildInfo로 로그 확인 |
| -30 | CL_INVALID_VALUE | 인수 값 잘못됨 | API 문서의 유효 범위 확인 |
| -34 | CL_INVALID_CONTEXT | 컨텍스트 객체 무효 | 클린업 순서 역전 여부 확인 |
| -36 | CL_INVALID_COMMAND_QUEUE | 큐 객체 무효 | 큐 생성 결과 코드 확인 |
| -44 | CL_INVALID_PROGRAM_EXECUTABLE | 빌드 안 된 프로그램 | clBuildProgram 선행 호출 확인 |
| -54 | CL_INVALID_WORK_GROUP_SIZE | local_size 초과 | CL_KERNEL_WORK_GROUP_SIZE 쿼리 후 조정 |
OpenCL 메모리 모델
OpenCL은 4단계 메모리 계층을 명시적으로 관리합니다. 글로벌 메모리(Global Memory)와 로컬 메모리(Local Memory) 간의 데이터 이동을 수동으로 제어하여 메모리 대역폭을 최적화합니다.
| 메모리 타입 | 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,
®ion, &err);
region.origin = TOTAL / 2;
cl_mem sub_b = clCreateSubBuffer(big_buf,
CL_MEM_READ_ONLY, CL_BUFFER_CREATE_TYPE_REGION,
®ion, &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);
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)로 지정하여 임베디드·모바일 구현의 진입 장벽을 낮추었습니다. 런타임에서 기능 쿼리를 통해 지원 여부를 확인한 뒤 코드 경로를 선택하는 방식이 필요합니다.
| 기능 | 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
두 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 Linux 드라이버 스택
Vulkan 애플리케이션이 GPU에 접근하는 전체 소프트웨어 스택은 다음과 같습니다.
Vulkan 로더(libvulkan.so)가 ICD(Installable Client Driver)를 검색하여
적절한 드라이버를 동적 로드하고, 드라이버는 DRM 렌더 노드 ioctl로 GPU와 통신합니다.
| 드라이버 | GPU | 출처 | Vulkan 버전 | Compute 지원 | DRM 드라이버 |
|---|---|---|---|---|---|
| RadV | AMD GCN 이후 | Mesa (오픈소스) | 1.3 | 완전 | amdgpu |
| ANV | Intel Gen8 이후 | Mesa (오픈소스) | 1.3 | 완전 | i915 / xe |
| NVK | NVIDIA Turing 이후 | Mesa (오픈소스) | 1.3 | 완전 | nouveau (GSP) |
| PanVK | ARM Mali (Valhall) | Mesa (오픈소스) | 1.0~1.1 | 부분적 | panfrost |
| Turnip | Qualcomm Adreno | Mesa (오픈소스) | 1.3 | 완전 | msm |
| V3DV | Broadcom VideoCore VI | Mesa (오픈소스) | 1.2 | 부분적 | v3d |
| lavapipe | CPU (소프트웨어) | Mesa (오픈소스) | 1.3 | 완전 | — (CPU 실행) |
| NVIDIA 독점 | NVIDIA Kepler 이후 | 독점 | 1.3 | 완전 | nvidia-drm |
| AMDGPU-PRO | AMD (프로) | 반독점 | 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
MESA_VK_DEVICE_SELECT=nouveau로 NVK를 명시적으로 선택합니다.
Compute Shader와 파이프라인 구성
Vulkan Compute Pipeline의 구성 과정은 크게 6단계로 나뉩니다: ① GLSL/HLSL 작성 → ② SPIR-V 컴파일 → ③ VkDevice/VkQueue 생성 → ④ 리소스 할당(버퍼, 디스크립터) → ⑤ 파이프라인 생성 → ⑥ 커맨드 기록 및 제출.
/* 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 / GLSL | 설명 |
|---|---|---|
| Grid | Dispatch (vkCmdDispatch) | 전체 실행 범위 |
| Block | Work-group (local_size) | 공유 메모리/배리어 범위 |
| Thread | Invocation | 개별 실행 단위 |
blockIdx | gl_WorkGroupID | 워크그룹 ID |
threadIdx | gl_LocalInvocationID | 로컬 인덱스 |
blockIdx*blockDim+threadIdx | gl_GlobalInvocationID | 글로벌 인덱스 |
__shared__ | shared | 워크그룹 내 공유 메모리 |
__syncthreads() | barrier() | 워크그룹 동기화 |
cudaMalloc | vkAllocateMemory + vkBindBufferMemory | 디바이스 메모리 할당 |
cudaMemcpy | vkMapMemory / vkCmdCopyBuffer | 호스트↔디바이스 전송 |
| CUDA Stream | VkQueue + VkFence/VkSemaphore | 비동기 실행/동기화 |
SPIR-V 셰이더 컴파일
SPIR-V(Standard Portable Intermediate Representation)는 Vulkan의 셰이더 바이트코드 형식입니다. GLSL·HLSL·Slang 등 고급 셰이딩 언어에서 SPIR-V로 컴파일한 뒤 Vulkan 드라이버에 전달하면, 드라이버가 대상 GPU의 네이티브 ISA(GCN/RDNA, Xe, 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
| 컴파일러 | 입력 | 출력 | 특징 |
|---|---|---|---|
| glslc (shaderc) | GLSL | SPIR-V | Google 개발, Vulkan SDK 포함, glslang 기반 |
| glslangValidator | GLSL | SPIR-V | Khronos 공식 참조 컴파일러 |
| DXC | HLSL | SPIR-V / DXIL | Microsoft 개발, SM 6.x 지원, HLSL 2021 |
| slangc | Slang | SPIR-V / DXIL / PTX | NVIDIA 개발, 자동 미분, 제네릭 |
| naga | WGSL / GLSL / SPIR-V | SPIR-V / MSL / GLSL / HLSL | Rust wgpu 생태계, 다중 백엔드 |
Vulkan 메모리 관리
Vulkan은 명시적 메모리 관리를 요구합니다. OpenGL이 드라이버에 위임하던 메모리 할당/바인딩을
애플리케이션이 직접 제어하며, 이를 통해 메모리 레이아웃과 전송을 최적화할 수 있습니다.
GPU 메모리는 VkPhysicalDeviceMemoryProperties로 조회한 메모리 타입과
힙(Heap) 정보를 기반으로 할당합니다.
| 플래그 | 의미 | 성능 특성 |
|---|---|---|
DEVICE_LOCAL | GPU 로컬 메모리 (VRAM) | GPU 접근 최고 대역폭, CPU 직접 접근 불가(일반적) |
HOST_VISIBLE | CPU에서 vkMapMemory 가능 | CPU 쓰기 가능, GPU 대역폭은 PCIe 제한 |
HOST_COHERENT | CPU 쓰기 즉시 GPU에 가시적 | vkFlushMappedMemoryRanges 불필요 |
HOST_CACHED | CPU 읽기 캐시 | CPU→GPU 쓰기는 느림, GPU→CPU 읽기 최적화 |
LAZILY_ALLOCATED | 지연 할당 (타일 기반 GPU) | 모바일 GPU의 on-chip 메모리 (실제 VRAM 미사용) |
maxMemoryAllocationCount를
보장하며, 일반적으로 4096개입니다. 작은 버퍼마다 vkAllocateMemory를 호출하면
이 제한에 빠르게 도달합니다. VMA(Vulkan Memory Allocator) 같은 서브할당자로
하나의 큰 할당에서 여러 버퍼를 서브할당하세요. VMA는 AMD가 개발한 오픈소스 라이브러리로,
vmaCreateBuffer() 한 줄로 버퍼 생성+메모리 할당+바인딩을 처리합니다.
Vulkan 동기화 모델
Vulkan의 동기화는 완전히 명시적입니다. 드라이버가 자동으로 동기화하는 OpenGL과 달리, 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);
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_SubgroupSize와 subgroupBallot() 등 내장 함수로 워프 수준 최적화를 수행합니다.
| 기능 | Vulkan 버전/확장 | CUDA 대응 | 설명 |
|---|---|---|---|
| Subgroup Operations | 1.1 코어 | Warp Intrinsics | 셔플, 투표, 리덕션 — 공유메모리 없이 워프 내 통신 |
| Push Constants | 1.0 코어 | 커널 인자 | 커맨드 버퍼에 인라인 상수 (최대 128~256B) |
| Specialization Constants | 1.0 코어 | 템플릿 파라미터 | 파이프라인 생성 시 SPIR-V 상수 주입 (JIT 최적화) |
| Descriptor Indexing | 1.2 코어 | — | 런타임 배열 인덱싱으로 바인딩리스 리소스 접근 |
| Buffer Device Address | 1.2 코어 | GPU 포인터 | GPU 메모리 주소를 정수로 전달 (포인터 산술) |
| Timeline Semaphore | 1.2 코어 | CUDA Event | 단조 증가 카운터로 세밀한 CPU/GPU 동기화 |
| Cooperative Matrix | VK_KHR_cooperative_matrix | WMMA / MMA | Tensor Core/Matrix Core 접근 (행렬 FMA) |
| Mesh Shaders | VK_EXT_mesh_shader | — | Compute-like 메시 처리 (그래픽 파이프라인) |
| Shader Int8/Float16 | 1.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 */
}
}
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 오용을 검출하는 것이 필수적입니다.
| 도구 | 용도 | 핵심 기능 |
|---|---|---|
VK_LAYER_KHRONOS_validation | API 유효성 검사 | 잘못된 파라미터, 동기화 오류, 메모리 누수 탐지 |
VK_LAYER_KHRONOS_synchronization2 | 동기화 검증 | 배리어 누락, 레이스 컨디션 경고 |
| RenderDoc | GPU 캡처/리플레이 | Compute Dispatch 상태 검사, 버퍼 내용 확인 |
| Nsight Graphics | NVIDIA GPU 프로파일링 | SM 활용률, 메모리 대역폭, 워프 분석 |
| Radeon GPU Profiler (RGP) | AMD GPU 프로파일링 | 파이프라인 타임라인, 웨이브 오큐펀시 |
| GPA (Intel) | Intel GPU 프로파일링 | EU 활용률, 메모리 대역폭 |
vulkaninfo | GPU 기능 조회 | 확장, 제한, 메모리 타입, 큐 패밀리 |
spirv-val | SPIR-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)
/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? |
|---|---|---|
| ML 추론 (모바일) | ncnn, MNN, ONNX Runtime | ARM Mali/Adreno에서 CUDA 불가, OpenCL 제한적 |
| ML 추론 (데스크톱) | llama.cpp (ggml), Kompute | NVIDIA/AMD/Intel 모든 GPU에서 LLM 추론 |
| 영상/이미지 처리 | FFmpeg (Vulkan 필터), darktable | 하드웨어 디코더(VkVideoDecodeKHR)와 통합 |
| 과학 계산 | VkFFT, VkCV | 크로스 벤더 FFT, 이미지 처리 |
| 게임 엔진 | Godot, Unreal Engine 5 | 그래픽과 컴퓨트 동일 API, 큐 오버랩 |
| UI 렌더링 | Zed (GPU UI), Flutter | GPU 가속 텍스트/레이아웃 연산 |
| 블록체인 | GPU 마이너 | 크로스 벤더 해시(Hash) 연산 |
| 항목 | CUDA | Vulkan Compute | OpenCL | ROCm/HIP | oneAPI/SYCL |
|---|---|---|---|---|---|
| 벤더 | NVIDIA 전용 | 크로스 벤더 | 크로스 벤더 | AMD (+ NVIDIA via HIP) | Intel (+ 크로스) |
| 추상화 수준 | 중간 | 매우 낮음 | 중간 | 중간 (CUDA 호환) | 높음 (C++17) |
| 셰이더/커널 | CUDA C (.cu) | SPIR-V (GLSL/HLSL) | OpenCL C / SPIR-V | HIP C++ (.hip) | SYCL/DPC++ |
| Tensor Core | WMMA, MMA PTX | VK_KHR_cooperative_matrix | — | Matrix Core (MFMA) | XMX |
| 디버깅 도구 | cuda-gdb, NSight | Validation Layer, RenderDoc | — | rocgdb, rocprof | oneAPI debugger |
| AI 생태계 | cuDNN, TensorRT, NCCL | ncnn, Kompute | — | MIOpen, RCCL | oneDNN |
| 보일러플레이트 | ~20줄 | ~300줄 | ~100줄 | ~20줄 | ~30줄 |
| 모바일 GPU | 불가 | Mali, Adreno, PowerVR | 제한적 | 불가 | 불가 |
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 커널 드라이버와 통신합니다.
각 계층의 역할은 다음과 같습니다.
HIP Runtime은 amdhip64 라이브러리로 제공되며,
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 | 설명 |
|---|---|---|
cudaMalloc | hipMalloc | 디바이스 메모리 할당 |
cudaFree | hipFree | 디바이스 메모리 해제 |
cudaMemcpy | hipMemcpy | 호스트↔디바이스 데이터 복사 |
cudaMemcpyAsync | hipMemcpyAsync | 비동기 메모리 복사 |
cudaDeviceSynchronize | hipDeviceSynchronize | 디바이스 동기화 |
cudaStreamCreate | hipStreamCreate | 스트림 생성 |
cudaEventCreate | hipEventCreate | 이벤트 생성 |
cudaGetDeviceProperties | hipGetDeviceProperties | 디바이스 속성 조회 |
cudaError_t | hipError_t | 오류 타입 |
cudaSuccess | hipSuccess | 성공 코드 |
threadIdx / blockIdx | threadIdx / blockIdx | 스레드·블록 인덱스 (동일) |
__global__ | __global__ | 커널 함수 한정자 (동일) |
__shared__ | __shared__ | 공유 메모리 (동일) |
__syncthreads() | __syncthreads() | 블록 내 동기화 배리어 (동일) |
atomicAdd | atomicAdd | 원자적 덧셈 (동일) |
kernel<<<grid,block>>>() | hipLaunchKernelGGL(...) 또는 <<<>>> | 커널 실행 |
cublasCreate | rocblas_create_handle | BLAS 핸들 생성 |
cudnnCreate | miopenCreate | DNN 라이브러리 초기화 |
curandCreateGenerator | hiprandCreateGenerator | 난수 생성기 |
cufftPlan1d | rocfft_plan_create | FFT 플랜 생성 |
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
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 — 공유 메모리), 스칼라 유닛, 스케줄러가 포함됩니다.
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
Intel oneAPI / Level Zero
Intel oneAPI는 CPU·GPU·FPGA를 통합하는 오픈 표준 프로그래밍 플랫폼입니다. Level Zero는 그 중 GPU와 직접 통신하는 저수준 API로, DRM render node(xe/i915 드라이버) 위에서 동작합니다. SYCL/DPC++ 컴파일러가 Level Zero를 백엔드로 사용합니다.
Level Zero 아키텍처
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)는 행렬 곱을 하드웨어에서 가속하는 전용 유닛입니다. 딥러닝 추론 성능을 크게 향상시킵니다.
| 레벨 | 크기 | 접근 범위 | 지연 |
|---|---|---|---|
| 레지스터 파일 | 256KB/EU | 단일 Execution Unit | 1 사이클 |
| 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
- DPC++ (Intel LLVM 기반) — SYCL C++ 소스 파싱
- SPIR-V 중간 표현 생성 (
-fsycl-targets=spir64) - Intel GPU OpenCL 드라이버 (NEO/ocloc) — SPIR-V → GPU ISA 컴파일
- Level Zero / OpenCL runtime — ISA를 GPU에 로딩 및 실행
/dev/dri/renderD128ioctl → 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 / 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++ | 별도 소스 파일 필요 | 런타임 문자열 커널 |
버퍼/접근자 방식 외에 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 (신형) | 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/ |
- 동일 시스템에서 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)로 변경되었습니다. 아래 표는 컴퓨트 관련 세대별 주요 스펙을 비교합니다.
| 세대 / 제품 | 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 |
- 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
- NVIDIA CUDA Documentation — CUDA 프로그래밍 가이드
- Khronos OpenCL Registry — OpenCL 스펙
- Khronos Vulkan Registry — Vulkan API 스펙
- AMD ROCm Documentation — ROCm 플랫폼 문서
- Intel oneAPI — oneAPI 플랫폼 소개
- Compute Accelerators — accel 디바이스 노드와 드라이버 모델
- Mesa 3D Graphics Library — OpenCL (Rusticl/clover), Vulkan 드라이버
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.