GPU 서브시스템 (DRM/KMS)

Linux GPU 서브시스템(DRM/KMS)을 디스플레이 파이프라인과 연산 가속 경로를 함께 보는 관점에서 심층 분석합니다. DRM core와 KMS 객체 모델(CRTC/plane/encoder/connector), GEM/TTM 메모리 관리자, DMA-BUF 기반 장치 간 버퍼 공유, atomic modesetting과 vblank 동기화, fence 기반 GPU 작업 순서 제어, 전원·클럭·열 관리, userspace(Mesa/Wayland)와의 ioctl 계약, debugfs/tracepoint로 프레임 드롭과 hang을 진단하는 방법까지 GPU 드라이버 개발 핵심을 다룹니다.

전제 조건: 디바이스 드라이버DMA 문서를 먼저 읽으세요. 멀티미디어/가속기 경로는 대용량 버퍼 이동과 동기화가 성능의 핵심이므로, 메모리 경로와 큐 모델을 먼저 파악해야 합니다.
일상 비유: 이 주제는 영상 제작 파이프라인과 비슷합니다. 촬영·편집·인코딩 단계가 끊기지 않아야 결과가 나오듯이, 버퍼 큐와 하드웨어 스케줄링의 연속성이 중요합니다.

핵심 요약

  • 노드 분리 — 화면 제어는 primary node, 비특권 렌더링은 render node, 비그래픽 연산은 accel node가 담당합니다.
  • 상태 모델 — KMS는 CRTC/plane/connector를 atomic state로 묶어 한 번에 검증하고 적용합니다.
  • 버퍼 수명주기 — BO 생성, 핸들 배정, mmap, DMA-BUF 공유, fence 동기화가 한 세트로 움직입니다.
  • 메모리 계층 — 단순 SoC GPU는 GEM shmem, 전용 VRAM GPU는 TTM/VRAM helper/자체 GPUVM을 주로 사용합니다.
  • 복구 설계 — hang 감지, fence 타임아웃, 엔진 리셋, 전원 재기동이 운영 안정성을 좌우합니다.

단계별 이해

  1. 어느 노드를 여는지부터 구분
    compositor는 primary node에서 KMS를 제어하고, 일반 앱은 render node에서 커맨드 제출을 시작합니다.
  2. 버퍼를 어떤 백엔드에 둘지 결정
    scanout 전용이면 dumb buffer, 일반 렌더링이면 드라이버 전용 BO, 장치 간 공유면 DMA-BUF까지 함께 봅니다.
  3. atomic 상태를 조립
    plane/CRTC/connector 속성을 새 상태 객체에 채운 뒤 atomic_check로 하드웨어 제약을 검증합니다.
  4. 렌더링과 표시를 동기화
    dma_resv, syncobj, IN_FENCE_FD/OUT_FENCE_PTR로 렌더 완료 시점을 맞춥니다.
  5. 운영 중 고장 경로를 준비
    GPU hang, hotplug, runtime suspend, reset recovery를 debugfs·tracepoint·drm_sched 타임아웃으로 추적합니다.
관련 표준: DisplayPort 2.1 (디스플레이 인터페이스), HDMI 2.1 (멀티미디어 인터페이스), E-EDID (디스플레이 정보 교환), PCIe 6.0 (GPU 인터커넥트) — DRM/KMS 서브시스템이 구현하는 디스플레이 및 GPU 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

DRM (Direct Rendering Manager) 개요

DRM은 Linux 커널의 GPU 접근을 관리하는 서브시스템입니다. 원래 3D 그래픽 가속을 위해 도입되었으나, 현재는 디스플레이 출력(KMS), GPU 메모리 관리(GEM/TTM), GPU 작업 스케줄링까지 포괄하는 핵심 프레임워크로 발전했습니다.

구성 요소역할커널 코드
DRM Core 드라이버 등록, ioctl 디스패치, 파일 오퍼레이션 drivers/gpu/drm/drm_*.c
KMS (Kernel Mode Setting) 디스플레이 모드 설정, CRTC/Encoder/Connector/Plane drivers/gpu/drm/drm_atomic*.c
GEM (Graphics Execution Manager) GPU 메모리 버퍼 할당/관리, mmap drivers/gpu/drm/drm_gem*.c
TTM (Translation Table Manager) VRAM/시스템 메모리 간 버퍼 이동, 페이징 drivers/gpu/drm/ttm/
DMA-BUF 디바이스 간 버퍼 공유 (GPU↔카메라↔디스플레이) drivers/dma-buf/
GPU Scheduler GPU 작업 큐 관리, 우선순위, 타임아웃 처리 drivers/gpu/drm/scheduler/
DRM vs fbdev: fbdev(drivers/video/fbdev/)는 단순 프레임버퍼만 제공하는 레거시 인터페이스입니다. DRM/KMS는 하드웨어 가속, 다중 디스플레이, vsync, overlay plane 등을 지원하며, fbdev는 DRM으로 대체되는 추세입니다. 메인라인 그래픽 드라이버 개발에서는 DRM/KMS 사용이 권장되며 사실상 표준 경로입니다.

디바이스 노드와 권한 모델

현대 DRM UAPI는 GPU 하나를 단일 문자 디바이스로만 노출하지 않습니다. 같은 하드웨어라도 화면 제어, 비특권 렌더링, 비그래픽 연산을 서로 다른 노드로 나누어 권한 경계와 사용자 공간 스택을 분리합니다. 이 구분을 이해하지 못하면 drm_file의 인증 상태, client capability, 버퍼 공유 규칙, compositor와 compute runtime의 역할이 한꺼번에 흐려집니다.

노드 종류대표 경로주 용도권한/제약
Primary /dev/dri/card0 KMS modeset, connector/plane/lease 제어, 레거시 ioctl DRM master 개념이 존재합니다. 보통 compositor, Xorg, display manager가 소유합니다.
Control /dev/dri/controlD64 과거 KMS 제어용으로 설계됨 메인라인 문서 기준으로 현재는 사실상 미사용입니다.
Render /dev/dri/renderD128 OpenGL/Vulkan/VA-API/OpenCL 등 비특권 렌더링과 GPGPU modeset 및 privileged ioctl 불가. DRM master 없이 열 수 있으며 PRIME 기반 공유가 중심입니다.
Accel /dev/accel/accel0 NPU/AI/신호 처리 같은 비그래픽 compute DRIVER_COMPUTE_ACCEL 전용 네임스페이스입니다. graphics 스택과 분리된 별도 major를 사용합니다.
Compositor / Display Server Mutter, KWin, Weston KMS atomic commit, lease, hotplug 3D / Media Client Mesa, Vulkan, VA-API, OpenCL 비특권 렌더링, offscreen, compute AI / NPU Runtime vendor UMD, inference runtime 비그래픽 명령 제출, buffer mapping Primary Node /dev/dri/card0 DRM master + KMS Render Node /dev/dri/renderD128 no modeset, no master Accel Node /dev/accel/accel0 compute-only namespace 공통 커널 인프라 GEM/TTM, DMA-BUF, dma_resv, syncobj, scheduler, GPUVM
/* 유저 공간은 node 종류와 client capability를 먼저 협상한다 */
int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1);

/* para-virtual GPU에서는 커서 plane hotspot capability가 추가로 필요할 수 있다 */
drmSetClientCap(fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1);
실무 해석: compositor는 대개 primary node에서 KMS를 소유하고, 앱이나 미디어 스택은 render node를 따로 열어 커맨드를 제출합니다. atomic capability를 켜면 drm_file.atomicdrm_file.universal_planes가 함께 의미를 가지며, writeback connector는 atomic 지원 위에 추가로 opt-in 해야 노출됩니다.

DRM 아키텍처

User Space Mesa / Vulkan libdrm Wayland / X11 GPGPU (OpenCL) V4L2 / GBM ioctl / mmap DRM Core /dev/dri/card0 (Primary) & /dev/dri/renderD128 (Render) KMS (Mode Setting) CRTC · Encoder · Connector · Plane GEM / TTM 메모리 관리 GPU Scheduler Job Queue · Fence · Timeout DMA-BUF DRM Driver (i915 / amdgpu / nouveau / panfrost / virtio-gpu ...) 드라이버별 HW 초기화, 커맨드 서브미션, IRQ, 전원 관리 MMIO / DMA GPU Hardware

유저 공간 그래픽 스택

DRM/KMS 위에서 동작하는 유저 공간 소프트웨어 스택의 구조입니다. 각 계층이 DRM을 어떻게 활용하는지 이해하면 전체 그래픽 파이프라인을 파악할 수 있습니다.

계층구성 요소DRM 사용 방식
애플리케이션 게임, 브라우저, 3D 뷰어 OpenGL/Vulkan API 호출 (직접 DRM 접근 안 함)
윈도우 시스템 Wayland compositor (Mutter, KWin, Weston) Primary node에서 KMS atomic commit 수행. 렌더링은 GBM + render node 또는 드라이버 전용 UAPI를 함께 사용
GL/VK 드라이버 Mesa (radeonsi, iris, panfrost, radv, anv) Render node로 GPU 커맨드 제출. 드라이버 고유 ioctl 사용
libdrm DRM ioctl 래퍼 라이브러리 DRM ioctl을 C 함수로 래핑 (drmModeAtomicCommit() 등)
GBM (Generic Buffer Manager) Mesa 제공, compositor용 버퍼 할당 gbm_bo_create()가 드라이버 backend를 통해 scanout/texture/render target용 BO를 생성. dumb buffer는 이보다 훨씬 제한적인 최소 경로
EGL / WSI GL 컨텍스트 관리 / Vulkan Window System Integration DMA-BUF로 렌더 결과를 compositor에 전달
Mesa 드라이버 이름 매핑:
  • Intel: GL=iris(Gen8+)/crocus(Gen4~7), Vulkan=anv
  • AMD: GL=radeonsi(GCN+), Vulkan=radv
  • ARM Mali: GL=panfrost(Midgard/Bifrost)/lima(Utgard), Vulkan=panvk
  • Qualcomm: GL/Vulkan=freedreno/turnip
  • Broadcom: GL=v3d(Pi4/5)/vc4(Pi0~3), Vulkan=v3dv

DRM 핵심 구조체

DRM 핵심 구조체 관계도 drm_device GPU 디바이스 1개 = drm_device 1개 /dev/dri/card0, /dev/dri/renderD128 drm_driver 드라이버 오퍼레이션 정의 driver_features, fops, ioctls driver * drm_file 프로세스별 open() 인스턴스 filp->private_data 마스터/인증 상태 관리 filelist (1:N) idr (GEM 핸들 테이블) handle → drm_gem_object 매핑 KMS (Mode Setting) 오브젝트들 mode_config drm_crtc 디스플레이 파이프라인 스캔아웃 타이밍 제어 drm_encoder 신호 인코딩 TMDS/LVDS/DSI/eDP drm_connector 물리 출력 포트 HDMI/DP/VGA/DSI drm_plane 이미지 레이어 Primary/Cursor/Overlay drm_framebuffer GEM BO 래퍼 format/modifier/pitches drm_property 속성 (Atomic 상태) rotation/alpha/zpos... GEM BO 참조

drm_device & drm_driver

drm_device는 하나의 GPU 디바이스를 나타내고, drm_driver는 해당 GPU 유형의 오퍼레이션을 정의합니다.

#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>

/* DRM 드라이버 정의 — 드라이버 전체에 하나 */
static const struct drm_driver my_gpu_driver = {
    .driver_features    = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC |
                          DRIVER_RENDER,
    /* GEM 오퍼레이션 */
    .gem_prime_import   = drm_gem_prime_import,
    .gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,

    /* DRM ioctl 테이블 (드라이버 고유 ioctl) */
    .ioctls             = my_gpu_ioctls,
    .num_ioctls         = ARRAY_SIZE(my_gpu_ioctls),

    /* /sys, /dev 노드 이름 */
    .name               = "my-gpu",
    .desc               = "My GPU DRM driver",
    .date               = "20260101",
    .major              = 1,
    .minor              = 0,

    .fops               = &my_gpu_fops,
};

drm_file (파일 프라이빗)

유저 프로세스가 DRM/accel 노드를 open하면 drm_file 구조체가 생성됩니다. 이 구조체는 “프로세스별 GPU 컨텍스트”라기보다 파일 디스크립터별 세션 상태에 가깝고, 인증 여부, master 여부, client capability, 핸들 테이블, 이벤트 큐를 함께 들고 있습니다.

/* drm_file 핵심 필드 발췌 (include/drm/drm_file.h) */
struct drm_file {
    bool               authenticated; /* 레거시 primary 노드 렌더 권한 */
    bool               universal_planes;
    bool               atomic;
    bool               writeback_connectors;
    bool               is_master;
    bool               supports_virtualized_cursor_plane;
    struct drm_minor   *minor;        /* primary/render/accel 중 어떤 노드를 열었는지 */
    struct idr         object_idr;    /* GEM 핸들 → drm_gem_object 매핑 */
    struct idr         syncobj_idr;   /* drm_syncobj 핸들 테이블 */
    struct list_head   pending_event_list;
    struct list_head   event_list;    /* vblank, page flip, out-fence 이벤트 */
    void              *driver_priv;   /* 드라이버별 프라이빗 데이터 */
};
authenticated 해석 주의:
  • primary node에서는 master/authentication이 여전히 의미가 있습니다. KMS 관련 많은 ioctl은 현재 master를 전제로 합니다.
  • render node는 modeset/global ioctl을 막고 DRM master 개념도 버립니다. 파일 권한만 맞으면 즉시 GPU 접근이 가능합니다.
  • accel node는 graphics stack과 구분된 compute 전용 경로이므로, display 관련 상태를 노출하지 않는 것이 설계 목표입니다.
DRIVER_RENDER를 설정한 드라이버만 render node를 만들고, DRIVER_COMPUTE_ACCEL는 별도 accel 네임스페이스를 사용합니다.

KMS (Kernel Mode Setting)

KMS는 커널에서 디스플레이 모드를 설정하는 프레임워크입니다. 기존의 유저 공간 모드 설정(UMS)과 달리, 커널이 직접 해상도/주사율/디스플레이 파이프라인을 제어하여 콘솔↔그래픽 전환 시 깜박임이 없고 다중 모니터를 안정적으로 관리합니다.

KMS 핵심 오브젝트

Framebuffer drm_framebuffer GEM 객체 참조 Plane drm_plane Primary / Overlay / Cursor CRTC drm_crtc 스캔아웃 + 타이밍 생성 감마/CTM Encoder drm_encoder 신호 변환 Connector drm_connector HDMI/DP/eDP EDID 읽기 Framebuffer GEM 객체를 감싸는 래퍼. 픽셀 포맷, stride, 오프셋 정보 보유. 여러 Plane에서 공유 가능. Plane (Primary / Overlay / Cursor) 프레임버퍼를 화면 위 특정 위치/크기로 배치. Primary는 필수, Overlay/Cursor는 하드웨어 지원 시 사용. CRTC (CRT Controller) Plane들을 합성하여 최종 프레임 생성. 수직/수평 타이밍(해상도/주사율) 관리. 감마 보정, 색 변환 매트릭스(CTM) 적용. Encoder → Connector Encoder: 디지털 신호를 HDMI/DP/LVDS 등으로 변환. Connector: 물리 포트 + EDID 모니터 정보 + 핫플러그 이벤트.
KMS 오브젝트구조체주요 콜백역할
CRTC drm_crtc atomic_check, atomic_flush, atomic_enable 스캔아웃 엔진. 해상도/주사율 타이밍 생성, Plane 합성
Encoder drm_encoder atomic_check, atomic_enable, atomic_disable 내부 디지털 신호를 외부 프로토콜(HDMI/DP)로 변환
Connector drm_connector detect, get_modes, atomic_check 물리적 출력 포트. EDID 파싱, 핫플러그 감지
Plane drm_plane atomic_check, atomic_update, atomic_disable 프레임버퍼를 화면에 배치. 스케일링, 회전, 블렌딩
Framebuffer drm_framebuffer create_handle, destroy GEM 객체 래퍼. 픽셀 포맷, stride, modifier 정보

Vblank 처리와 페이지 플립

Vblank(수직 귀선 기간)은 디스플레이가 한 프레임을 마치고 다음 프레임을 시작하기 직전의 시간 구간입니다. 이 시점에 프레임버퍼를 교체(page flip)해야 화면 찢어짐(tearing)이 발생하지 않습니다. DRM 코어는 drm_vblank 인프라로 정확한 vblank 타이밍과 시퀀스 번호를 관리합니다.

/* Vblank 관련 드라이버 콜백 */
static const struct drm_crtc_funcs my_crtc_funcs = {
    /* vblank 카운터/타임스탬프 제공 */
    .get_vblank_counter  = drm_crtc_vblank_count,
    .enable_vblank       = my_enable_vblank,   /* vblank IRQ 활성화 */
    .disable_vblank      = my_disable_vblank,  /* vblank IRQ 비활성화 */
    .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
};

/* 드라이버 vblank IRQ 핸들러 */
static irqreturn_t my_irq_handler(int irq, void *data)
{
    struct drm_device *dev = data;
    u32 status = readl(regs + IRQ_STATUS);

    if (status & VBLANK_IRQ) {
        drm_crtc_handle_vblank(&my_crtc);  /* vblank 카운터 증가 + 이벤트 전달 */
    }
    return IRQ_HANDLED;
}

/* 페이지 플립 완료 알림 (vblank IRQ에서 호출) */
if (page_flip_pending) {
    drm_crtc_send_vblank_event(&my_crtc, event);
    drm_crtc_vblank_put(&my_crtc);
    page_flip_pending = false;
}
함수역할호출 위치
drm_crtc_vblank_on()CRTC 활성화 시 vblank 추적 시작CRTC enable 콜백
drm_crtc_vblank_off()CRTC 비활성화 시 vblank 추적 중단CRTC disable 콜백
drm_crtc_vblank_get()vblank IRQ 참조 획득 (카운팅)페이지 플립 요청 시
drm_crtc_vblank_put()vblank IRQ 참조 해제플립 완료 후
drm_crtc_handle_vblank()vblank 카운터 증가, 대기 중인 이벤트 처리vblank IRQ 핸들러
drm_crtc_send_vblank_event()유저에게 페이지 플립 완료 이벤트 전달플립 완료 시점
정밀 vblank 타임스탬프: drm_crtc_vblank_helper_get_vblank_timestamp()는 하드웨어 스캔라인 카운터를 읽어 vblank의 정확한 시각을 계산합니다. 이 타임스탬프는 DRM_EVENT_FLIP_COMPLETE 이벤트에 포함되어 유저 공간(Wayland compositor 등)이 프레임 타이밍을 정밀하게 제어하는 데 사용됩니다. 고정밀 프레임 스케줄링의 핵심 인프라입니다.

Atomic Modesetting

Atomic modesetting은 KMS의 현대적 커밋 모델입니다. 여러 디스플레이 속성 변경을 하나의 원자적 트랜잭션으로 묶어 중간 상태 없이 적용하거나 전체를 롤백합니다.

Atomic 상태 객체 그래프

실제 atomic UAPI의 핵심은 “property를 바로 레지스터에 쓰는 것”이 아니라, drm_atomic_state 아래에 CRTC/plane/connector/private object의 새 상태를 모아 둔 뒤 한 번에 검증하고 커밋하는 것입니다. driver의 atomic_check는 이 그래프 전체를 보고 대역폭, scaler 수, watermark, 링크 훈련 가능 여부, color LUT 크기, writeback 출력 버퍼 충돌까지 판단합니다.

상태 객체대표 구조체주요 내용
트랜잭션 컨테이너 drm_atomic_state 이번 커밋에서 바뀌는 모든 object의 old/new state 포인터와 acquire context를 묶습니다.
CRTC 상태 drm_crtc_state mode, active 여부, color pipeline, vrr, out-fence, bandwidth 결과를 담습니다.
Plane 상태 drm_plane_state framebuffer, source/destination rectangle, rotation, zpos, in-fence를 담습니다.
Connector 상태 drm_connector_state 연결된 CRTC, link status, colorspace, writeback job, content protection 상태를 담습니다.
드라이버 private 상태 drm_private_obj 계열 watermark, shared DPLL, DSC slice allocator처럼 여러 오브젝트가 공유하는 HW 자원을 모델링합니다.
drm_atomic_state 이번 커밋의 old/new 상태 묶음 drm_crtc_state mode, active, vrr, color bandwidth / out-fence drm_plane_state fb, src/dst rect, zpos rotation / in-fence drm_connector_state link status, colorspace writeback / content protection private obj state watermark, DPLL, DSC shared HW allocator atomic_check → atomic_commit 검증 단계는 그래프 전체를 보고 실패 시 폐기, 성공 시 commit tail에서 HW 순서대로 적용
/* Atomic 커밋 흐름 (커널 내부) */

/* 1. 유저가 DRM_IOCTL_MODE_ATOMIC 호출 */
/*    → drm_mode_atomic_ioctl() 진입 */

/* 2. 상태 복제 */
struct drm_atomic_state *state = drm_atomic_state_alloc(dev);
/* 각 오브젝트의 현재 상태를 복제하여 new_state 생성 */

/* 3. 속성 적용 (유저가 요청한 변경사항) */
drm_atomic_set_crtc_for_connector(new_conn_state, crtc);
new_crtc_state->mode_blob = mode;
new_plane_state->fb = framebuffer;
new_plane_state->crtc = crtc;

/* 4. 검증 (atomic_check) — 하드웨어 제약 확인 */
ret = drm_atomic_check_only(state);
/* 각 CRTC/Plane/Connector의 atomic_check 콜백 호출 */
/* 실패 시 -EINVAL 반환, 유저에게 거부 통보 */

/* 5. TEST_ONLY 플래그면 여기서 종료 (검증만 수행) */
if (flags & DRM_MODE_ATOMIC_TEST_ONLY)
    return ret;

/* 6. 커밋 (atomic_commit) — 실제 하드웨어 적용 */
ret = drm_atomic_commit(state);
/* NONBLOCK 플래그: 비동기 커밋 (vsync 대기 안 함) */
/* PAGE_FLIP_EVENT: 완료 시 유저에게 이벤트 전달 */
Atomic Modesetting 커밋 상태 머신 User IOCTL 요청 State 복제(Dup) Property 값 설정 atomic_check HW 제약 검증 실패 Rollback 상태 폐기 성공 TEST ONLY? Yes Return 0 검증 완료 No Commit atomic_commit HW Apply 레지스터 적용 Fence/Event FLIP_COMPLETE 범례: 성공 경로 (검증 통과 → 커밋) 실패 경로 (검증 실패 → 롤백) TEST_ONLY 경로 (검증만 수행)
Atomic 커밋 플래그:
  • DRM_MODE_ATOMIC_TEST_ONLY — 검증만 수행, 실제 적용 안 함. 유저 공간에서 구성 유효성을 미리 확인
  • DRM_MODE_ATOMIC_NONBLOCK — 비동기 커밋. vsync을 기다리지 않고 즉시 반환
  • DRM_MODE_PAGE_FLIP_EVENT — 플립 완료 시 DRM_EVENT_FLIP_COMPLETE 이벤트를 유저에게 전달
  • DRM_MODE_ATOMIC_ALLOW_MODESET — 해상도/주사율 변경이 포함된 full modeset 허용

DRM Properties 시스템

KMS 오브젝트(CRTC, Plane, Connector)의 모든 설정 가능한 속성은 DRM Property로 표현됩니다. Atomic modesetting에서는 property ID와 값을 쌍으로 전달하여 디스플레이 상태를 변경합니다.

Property 타입

타입커널 생성 함수값 형태예시
Range drm_property_create_range() min~max 정수 alpha (0~0xFFFF), rotation angle
Enum drm_property_create_enum() 이름↔값 열거 DPMS (On/Standby/Suspend/Off)
Bitmask drm_property_create_bitmask() 비트 조합 rotation (ROTATE_0 | REFLECT_X)
Blob drm_property_create_blob() 임의 바이너리 데이터 MODE_ID (drm_mode_modeinfo), EDID
Object drm_property_create_object() 다른 KMS 오브젝트 ID CRTC_ID, FB_ID, IN_FENCE_FD
Signed Range drm_property_create_signed_range() 부호 있는 정수 SRC_X, SRC_Y (16.16 고정소수점)

표준 KMS Property

/* === CRTC Properties === */
"ACTIVE"           /* bool: CRTC 활성화 (atomic에서 DPMS 대체) */
"MODE_ID"          /* blob: 디스플레이 모드 (해상도/주사율) */
"OUT_FENCE_PTR"    /* ptr: 커밋 완료 fence fd 반환 위치 */
"VRR_ENABLED"      /* bool: 가변 주사율 활성화 */
"DEGAMMA_LUT"      /* blob: 디감마 LUT (Color Management) */
"CTM"              /* blob: 색 변환 매트릭스 (3x3, S31.32 고정소수) */
"GAMMA_LUT"        /* blob: 감마 LUT */

/* === Plane Properties === */
"FB_ID"            /* object: 표시할 프레임버퍼 */
"CRTC_ID"         /* object: 연결할 CRTC */
"SRC_X/Y/W/H"     /* range: 소스 영역 (16.16 고정소수점) */
"CRTC_X/Y/W/H"    /* range: 화면 위 위치/크기 (스케일링) */
"rotation"         /* bitmask: ROTATE_0/90/180/270 | REFLECT_X/Y */
"alpha"            /* range: 투명도 (0=투명, 0xFFFF=불투명) */
"pixel blend mode" /* enum: None/Pre-multiplied/Coverage */
"zpos"             /* range: 레이어 순서 (높을수록 위) */
"IN_FENCE_FD"      /* range: explicit sync 입력 fence fd */
"COLOR_ENCODING"   /* enum: YCbCr BT.601/709/2020 */
"COLOR_RANGE"      /* enum: YCbCr Limited/Full Range */

/* === Connector Properties === */
"CRTC_ID"          /* object: 연결할 CRTC */
"DPMS"             /* enum: On/Standby/Suspend/Off (레거시) */
"link-status"      /* enum: Good/Bad — 링크 학습 실패 시 Bad */
"EDID"             /* blob: 모니터 EDID 바이너리 (읽기 전용) */
"content type"     /* enum: No Data/Graphics/Photo/Cinema/Game */
"max bpc"          /* range: 최대 색 깊이 (bits per component) */
"HDR_OUTPUT_METADATA" /* blob: HDR 정적/동적 메타데이터 */
"vrr_capable"      /* range: 모니터 VRR 지원 여부 (읽기 전용) */
/* 드라이버에서 커스텀 property 등록 */
struct drm_property *prop;

/* Enum property 생성 */
static const struct drm_prop_enum_list my_scaling_modes[] = {
    { 0, "None" },
    { 1, "Full" },
    { 2, "Center" },
    { 3, "Full aspect" },
};
prop = drm_property_create_enum(dev, 0, "scaling mode",
        my_scaling_modes, ARRAY_SIZE(my_scaling_modes));

/* CRTC에 property 연결 */
drm_object_attach_property(&crtc->base, prop, 0);

/* atomic_check에서 property 값 읽기 */
static int my_crtc_atomic_check(struct drm_crtc *crtc,
                                struct drm_atomic_state *state)
{
    struct drm_crtc_state *crtc_state =
        drm_atomic_get_new_crtc_state(state, crtc);
    /* crtc_state에서 property 값에 접근 */
    ...
}
Property 조회 도구: modetest -M <driver> -p로 모든 KMS 오브젝트의 property와 현재 값을 확인할 수 있습니다. 유저 공간에서는 DRM_IOCTL_MODE_GETPROPERTY로 property 메타데이터를, DRM_IOCTL_MODE_OBJ_GETPROPERTIES로 오브젝트별 값을 조회합니다.

Color Management 파이프라인

KMS는 CRTC 레벨에서 3단계 색상 처리 파이프라인을 제공합니다. 이 파이프라인으로 HDR 톤매핑, 색 공간 변환, 감마 보정 등을 하드웨어 가속으로 수행합니다.

Plane 합성된 픽셀 1. Degamma LUT DEGAMMA_LUT 비선형 → 선형 변환 (sRGB/PQ EOTF 역변환) 2. CTM Color Transform 3×3 매트릭스 곱셈 (색 공간 변환) 3. Gamma LUT GAMMA_LUT 선형 → 비선형 변환 (디스플레이 감마 적용) 출력 Encoder LUT 크기: DEGAMMA_LUT_SIZE, GAMMA_LUT_SIZE property로 하드웨어 지원 엔트리 수 확인 (읽기 전용). 일반적으로 256 또는 4096 엔트리. CTM은 항상 3×3 = 9개 S31.32 고정소수점 값 (struct drm_color_ctm).
/* Color Management 관련 구조체 */

/* 감마/디감마 LUT 엔트리 (유저 공간에서 설정) */
struct drm_color_lut {
    __u16 red;      /* 0~0xFFFF */
    __u16 green;
    __u16 blue;
    __u16 reserved;
};

/* 색 변환 매트릭스 (CTM) — S31.32 고정소수점 */
struct drm_color_ctm {
    __u64 matrix[9];   /* 3x3, 부호 비트 + 31.32 고정소수 */
};

/* 유저 공간에서 감마 LUT 설정 예시 (libdrm) */
struct drm_color_lut lut[256];
for (int i = 0; i < 256; i++) {
    /* sRGB 감마 커브: 선형 → sRGB 비선형 */
    double v = (double)i / 255.0;
    double srgb = (v <= 0.0031308) ?
        v * 12.92 : 1.055 * pow(v, 1.0/2.4) - 0.055;
    __u16 val = (__u16)(srgb * 0xFFFF);
    lut[i].red = lut[i].green = lut[i].blue = val;
}
/* blob property로 설정 */
drmModeCreatePropertyBlob(fd, lut, sizeof(lut), &blob_id);
/* atomic 커밋에 GAMMA_LUT=blob_id 추가 */
HDR 지원: HDR10에서는 HDR_OUTPUT_METADATA connector property로 SMPTE ST 2086 마스터링 디스플레이 정보와 MaxCLL/MaxFALL 값을 설정합니다. PQ(Perceptual Quantizer) EOTF는 Degamma LUT로 처리하며, BT.2020 색 공간 변환은 CTM으로 수행합니다. 하드웨어가 충분한 LUT 정밀도(최소 1024 엔트리)를 제공해야 정확한 HDR 재현이 가능합니다.

VRR (Variable Refresh Rate) / Adaptive Sync

VRR은 GPU 렌더링 속도에 맞춰 디스플레이 주사율을 동적으로 조절하는 기술입니다. 화면 찢어짐(tearing)과 스터터링(stuttering) 없이 부드러운 프레임 전달을 가능하게 합니다.

기술표준DRM 지원설명
Adaptive-Sync VESA DP Adaptive-Sync DisplayPort connector DP 표준의 VRR. FreeSync 모니터가 주로 사용
HDMI VRR HDMI 2.1 VRR HDMI connector HDMI Forum VRR. QFT (Quick Frame Transport) 포함
Panel Self Refresh eDP PSR/PSR2 eDP connector 정적 화면에서 패널이 자체 리프레시 → GPU 절전
/* VRR 활성화 흐름 (커널 내부) */

/* 1. Connector가 VRR 지원하는지 확인 */
/*    모니터 EDID의 Adaptive-Sync range 파싱 */
connector->vrr_capable = true;  /* 드라이버가 EDID 기반으로 설정 */

/* 2. 유저 공간에서 CRTC의 VRR_ENABLED property 설정 */
/*    → atomic commit에 포함 */
new_crtc_state->vrr_enabled = true;

/* 3. 드라이버 atomic_check에서 VRR 설정 검증 */
static int my_crtc_atomic_check(struct drm_crtc *crtc,
                                struct drm_atomic_state *state)
{
    struct drm_crtc_state *crtc_state =
        drm_atomic_get_new_crtc_state(state, crtc);

    if (crtc_state->vrr_enabled) {
        /* VRR 범위 확인 (min_vfreq ~ max_vfreq) */
        struct drm_connector *conn = get_connector(crtc_state);
        if (!conn->display_info.monitor_range.max_vfreq)
            return -EINVAL;

        /* 하드웨어 VRR 타이밍 설정 */
        my_state->vmin = conn->display_info.monitor_range.min_vfreq;
        my_state->vmax = conn->display_info.monitor_range.max_vfreq;
    }
    return 0;
}

/* 4. 페이지 플립 시 VRR 타이밍 적용 */
/*    GPU가 프레임을 완료하면 즉시 vsync 발생 */
/*    → 가변 vblank 주기 */
Fixed Refresh vs VRR (Variable Refresh Rate) 타이밍 비교 Fixed (60Hz) VB VB VB VB VB VB VB 빈 공간 = 프레임 대기 (스터터링) | 빨간 바 = 프레임 초과 (티어링/드롭) VRR (48~144Hz) VB VB VB VB VB VB VB 짧음 길음 짧음 Vblank 간격이 프레임 렌더링 시간에 맞춰 동적으로 조절 → 스터터링/티어링 없음 프레임 렌더링 시간 VB = Vblank (Fixed: 고정 간격) VB = Vblank (VRR: 가변 간격)
VRR 확인 명령:
  • modetest -M amdgpu -p에서 vrr_capable property 확인
  • cat /sys/class/drm/card0-DP-1/vrr_capable — sysfs에서 직접 조회
  • Wayland에서는 wp_tearing_control_v1 프로토콜로 VRR/tearing 제어
  • X11에서는 xrandr --prop으로 VRR 속성 확인

Format Modifier (타일링/압축)

GPU는 메모리 접근 효율을 위해 픽셀 데이터를 단순 행(linear) 방식이 아닌 타일(tile) 형태로 배치합니다. Format Modifier는 프레임버퍼의 메모리 레이아웃을 기술하는 64비트 토큰입니다.

/* Format Modifier 구조 (include/uapi/drm/drm_fourcc.h) */
/*
 * [63:56] = vendor (DRM_FORMAT_MOD_VENDOR_*)
 * [55:0]  = vendor별 modifier 값
 *
 * 0 = DRM_FORMAT_MOD_LINEAR (행 순차 배치, 모든 디바이스 공통)
 */

#define DRM_FORMAT_MOD_LINEAR  0

/* Intel 타일링 modifier 예시 */
#define I915_FORMAT_MOD_X_TILED    /* X-tiling (레거시) */
#define I915_FORMAT_MOD_Y_TILED    /* Y-tiling (Gen9+) */
#define I915_FORMAT_MOD_4_TILED    /* Tile-4 (Gen12.5+, DG2/MTL) */
#define I915_FORMAT_MOD_Y_TILED_CCS /* Y-tiling + CCS 압축 */

/* AMD modifier 예시 */
#define AMD_FMT_MOD                /* 타일 버전, pipe_xor_bits, DCC 등 인코딩 */

/* ARM AFBC (Arm Frame Buffer Compression) */
#define DRM_FORMAT_MOD_ARM_AFBC    /* Mali GPU 프레임버퍼 무손실 압축 */

/* Broadcom */
#define DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED /* VideoCore T-tiling */

/* NVIDIA */
#define DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D /* GOB 기반 타일링 */
Format Modifier: 메모리 레이아웃 비교 Linear (행 순차) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 메모리 순서 메모리: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 행별 연속 Tiled (4x2 타일) Tile A 0 1 2 3 8 9 10 11 Tile B 4 5 6 7 12 13 14 15 메모리: 0 1 2 3 8 9 10 11 4 5 6 7 12 13 14 15 타일별 연속 Compressed (타일+압축) Tile A (CCS/DCC) 압축 데이터 0~3, 8~11 Tile B (CCS/DCC) 압축 데이터 4~7, 12~15 Meta 압축 맵 메모리: A(압축) B(압축) Meta 대역폭 30~50% 절감 압축+메타 메모리 대역폭 사용량 비교 Linear 100% Tiled ~85% Compressed ~50~70%
레이아웃설명장점단점
Linear 행 순차 배치 (pitch × height) 모든 디바이스 호환, 단순 GPU 캐시 효율 낮음
Tiled 정사각 또는 직사각 타일 단위 배치 2D 공간 지역성 향상, GPU 캐시 효율 디바이스별 포맷 비호환
Compressed 타일링 + 무손실 압축 (CCS, DCC, AFBC) 메모리 대역폭 절감 30~50% 디바이스 간 공유 시 압축 해제 필요
/* 프레임버퍼 생성 시 modifier 지정 (ADDFB2) */
struct drm_mode_fb_cmd2 cmd = {
    .width   = 1920,
    .height  = 1080,
    .pixel_format = DRM_FORMAT_XRGB8888,
    .handles[0] = gem_handle,
    .pitches[0] = pitch,
    .modifier[0] = I915_FORMAT_MOD_Y_TILED, /* modifier 지정 */
    .flags   = DRM_MODE_FB_MODIFIERS,       /* modifier 사용 플래그 */
};
drmIoctl(fd, DRM_IOCTL_MODE_ADDFB2, &cmd);

/* Plane이 지원하는 modifier 목록 조회 */
/* DRM_IOCTL_MODE_GETPLANE2 또는 IN_FORMATS blob property */
/* → (format, modifier) 쌍의 목록 반환 */

/* 드라이버: Plane에 지원 modifier 등록 */
static const uint64_t my_modifiers[] = {
    DRM_FORMAT_MOD_LINEAR,
    I915_FORMAT_MOD_Y_TILED,
    I915_FORMAT_MOD_Y_TILED_CCS,
    DRM_FORMAT_MOD_INVALID  /* 종료 마커 */
};
drm_universal_plane_init(dev, plane, ...,
    formats, num_formats, my_modifiers, ...);
Modifier 호환성: DMA-BUF로 버퍼를 공유할 때 modifier가 다르면 importer가 버퍼를 읽을 수 없습니다. 디바이스 간 버퍼 공유 시에는 DRM_FORMAT_MOD_LINEAR를 사용하거나, 양쪽 디바이스가 모두 지원하는 modifier를 협상해야 합니다. Wayland에서는 zwp_linux_dmabuf_v1 프로토콜로 compositor와 클라이언트 간 modifier를 협상합니다.

GEM (Graphics Execution Manager) 메모리 관리

GEM은 GPU 메모리 버퍼 객체를 관리하는 프레임워크입니다. 유저 공간에서는 정수 핸들로 버퍼를 참조하고, 커널은 내부적으로 drm_gem_object로 관리합니다.

/* drm_gem_object 핵심 필드 발췌 (include/drm/drm_gem.h) */
struct drm_gem_object {
    struct kref           refcount;      /* 참조 카운팅 */
    unsigned             handle_count;  /* 유저 핸들 수 */
    struct drm_device     *dev;          /* 소속 디바이스 */
    struct file           *filp;         /* shmem backing store 또는 NULL */
    struct drm_vma_offset_node vma_node; /* mmap 오프셋 관리 */
    size_t                size;          /* 버퍼 크기 */
    int                   name;          /* flink 이름 (레거시) */
    struct dma_buf        *dma_buf;      /* PRIME export/import 연결점 */
    struct dma_buf_attachment *import_attach;
    struct dma_resv       *resv;         /* 공유 fence 컨테이너 */
    const struct drm_gem_object_funcs *funcs;
};

/* GEM 오퍼레이션 콜백 */
struct drm_gem_object_funcs {
    void (*free)(struct drm_gem_object *obj);
    int  (*open)(struct drm_gem_object *obj, struct drm_file *file);
    void (*close)(struct drm_gem_object *obj, struct drm_file *file);
    int  (*pin)(struct drm_gem_object *obj);
    int  (*vmap)(struct drm_gem_object *obj, struct iosys_map *map);
    struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
};
dumb buffer와 일반 BO를 같은 것으로 보면 안 됩니다: DRM_IOCTL_MODE_CREATE_DUMB는 CPU mmap과 scanout을 위한 최소 공통 경로입니다. 반면 GBM이나 vendor UAPI가 만드는 렌더 타깃 BO는 tiling/modifier, 압축 메타데이터, 엔진별 배치 제약, 수입된 DMA-BUF attachment까지 함께 고려합니다.

GEM 버퍼 생명주기

단계ioctl / 함수설명
생성 DRM_IOCTL_MODE_CREATE_DUMB 비가속 dumb 버퍼 생성 (KMS 전용). 드라이버별 ioctl로 가속 버퍼 생성
매핑 DRM_IOCTL_MODE_MAP_DUMBmmap() 가짜 오프셋 반환 후 mmap으로 유저 공간에 매핑
공유 DRM_IOCTL_PRIME_HANDLE_TO_FD GEM 핸들을 DMA-BUF fd로 변환하여 다른 디바이스와 공유
임포트 DRM_IOCTL_PRIME_FD_TO_HANDLE 외부 DMA-BUF fd를 로컬 GEM 핸들로 변환
해제 DRM_IOCTL_GEM_CLOSE 핸들 해제. 참조 카운트가 0이 되면 실제 메모리 반환
GEM 핸들 테이블과 DMA-BUF 기반 버퍼 공유 Process A (GPU 클라이언트) drm_file → object_idr (핸들 테이블) handle 1 GEM A handle 2 GEM B handle 3 GEM C drm_gem A 4MB VRAM drm_gem B 8MB shmem drm_gem C 2MB GTT PRIME_HANDLE_TO_FD (handle 2 → fd 전달) DMA-BUF fd 커널 dma_buf 객체 → drm_gem B 참조 (refcount 증가) export import Process B (디스플레이 서버) drm_file → object_idr (핸들 테이블) handle 1 GEM B' handle 2 GEM D drm_gem B 동일 객체! drm_gem D 자체 버퍼 PRIME_FD_TO_HANDLE (fd → 로컬 handle 변환) 동일한 물리 메모리 (zero-copy 공유)

GEM SHMEM 헬퍼

대부분의 임베디드/모바일 GPU 드라이버는 drm_gem_shmem_object를 사용합니다. 시스템 메모리(shmem)에 버퍼를 할당하며, CMA(Contiguous Memory Allocator) 통합도 지원합니다.

#include <drm/drm_gem_shmem_helper.h>

/* GEM SHMEM 기반 드라이버의 dumb_create 구현 */
static int my_dumb_create(struct drm_file *file,
                           struct drm_device *dev,
                           struct drm_mode_create_dumb *args)
{
    /* pitch 정렬 (하드웨어 요구사항에 맞춤) */
    args->pitch = ALIGN(args->width * DIV_ROUND_UP(args->bpp, 8), 64);
    args->size  = args->pitch * args->height;

    /* shmem 헬퍼가 할당 + 핸들 반환 */
    return drm_gem_shmem_dumb_create(file, dev, args);
}

/* 드라이버에서 vmap으로 커널 가상 주소 획득 */
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gem_obj);
struct iosys_map map;
drm_gem_shmem_vmap(shmem, &map);
/* map.vaddr로 커널에서 버퍼 접근 */

TTM (Translation Table Manager)

TTM은 전용 VRAM(비디오 메모리)이 있는 GPU를 위한 메모리 관리자입니다. 버퍼를 VRAM과 시스템 메모리 사이에서 이동(eviction/migration)시키는 고급 기능을 제공합니다.

메모리 관리자백업 스토리지VRAM 지원사용 드라이버
GEM (shmem) 시스템 메모리 (shmem/CMA) 없음 panfrost, lima, vc4, v3d, virtio-gpu
GEM + VRAM helper 시스템 + VRAM 단순 ast, simpledrm, hibmc
TTM 시스템 + VRAM + GART 전체 (eviction, migration) amdgpu, nouveau, i915 (부분), vmwgfx, radeon
/* TTM 메모리 도메인 기술자 */
struct ttm_place {
    uint32_t  mem_type;   /* TTM_PL_SYSTEM, TTM_PL_VRAM, TTM_PL_TT */
    uint32_t  flags;      /* TTM_PL_FLAG_CONTIGUOUS 등 */
    uint64_t  fpfn;       /* 시작 페이지 프레임 번호 */
    uint64_t  lpfn;       /* 끝 페이지 프레임 번호 (0=제한 없음) */
};

/* ttm_buffer_object 핵심 필드 발췌 */
struct ttm_buffer_object {
    struct drm_gem_object    base;         /* GEM 상속 */
    struct ttm_device        *bdev;         /* TTM 디바이스 */
    enum ttm_bo_type        type;
    uint32_t                 page_alignment;
    void (*destroy)(struct ttm_buffer_object *bo);
    struct kref              kref;
    struct ttm_resource      *resource;     /* 현재 메모리 위치 */
    struct ttm_tt            *ttm;          /* 시스템 메모리 페이지 */
    bool                     deleted;
    unsigned                 priority;
    unsigned                 pin_count;
    struct sg_table         *sg;
};

/* TTM 메모리 도메인 상수 */
#define TTM_PL_SYSTEM  0  /* 시스템 RAM (커널 관리) */
#define TTM_PL_TT      1  /* GART/GTT 매핑된 시스템 메모리 */
#define TTM_PL_VRAM    2  /* 디바이스 전용 비디오 메모리 */
placement 해석: ttm_place/ttm_placement는 “이번 validate에서 허용할 위치 후보”를 설명하는 정책 객체입니다. 현재 실제 위치는 ttm_buffer_object.resource가 나타내며, TTM은 이 값과 새 placement 후보를 비교해 VRAM↔TT↔SYSTEM migration을 결정합니다.
TTM 메모리 도메인과 Eviction(퇴거) 흐름 VRAM (TTM_PL_VRAM) 디바이스 전용 비디오 메모리 BO-A 활성 (사용중) BO-B 활성 (사용중) BO-C 유휴 (LRU) BO-D 유휴 (LRU) VRAM 사용량: 90% (부족!) Eviction 발동 GTT/TT (TTM_PL_TT) GART 매핑된 시스템 메모리 BO-E GPU 접근 가능 BO-C' 퇴거됨 IOMMU/GART로 GPU가 시스템 메모리에 접근 GTT 사용량: 50% System (TTM_PL_SYSTEM) 일반 시스템 RAM BO-F CPU 전용 BO-D' 스왑 가능 GPU 직접 접근 불가 CPU mmap으로 접근 System 사용량: 30% evict migrate (접근 시) evict migrate Eviction (VRAM 부족 시 LRU 기반 퇴거) Migration (GPU 접근 시 자동 복귀) ttm_device_funcs.evict_flags 콜백으로 제어
TTM Eviction (퇴거): VRAM이 부족하면 TTM은 사용 빈도가 낮은 버퍼를 시스템 메모리로 이동시킵니다. 드라이버는 ttm_device_funcs.evict_flags 콜백으로 퇴거 정책을 설정합니다. GPU가 해당 버퍼에 접근하면 다시 VRAM으로 마이그레이션됩니다.

DMA-BUF (버퍼 공유)

심화 학습: DMA-BUF의 exporter/importer 구조, dma_fence/dma_resv, DMA-BUF Heaps 및 DMA 서브시스템 전체 종합 가이드는 DMA 심화 페이지를 참조하십시오.

DMA-BUF는 디바이스 간 버퍼 공유를 위한 커널 프레임워크입니다. GPU에서 렌더링한 버퍼를 디스플레이 컨트롤러, 비디오 인코더, 카메라 등 다른 디바이스와 복사 없이 공유할 수 있습니다.

Exporter (GPU 드라이버) 버퍼 소유자 DMA-BUF (fd) struct dma_buf + sg_table Display (KMS) V4L2 (카메라) 다른 GPU export import Implicit Fence (dma_resv) 동기화: GPU 작업 완료 대기 User Space: SCM_RIGHTS로 fd 전달 또는 PRIME ioctl
/* DMA-BUF exporter 오퍼레이션 (드라이버 구현) */
static const struct dma_buf_ops my_dmabuf_ops = {
    .attach         = my_dmabuf_attach,       /* importer 디바이스 등록 */
    .detach         = my_dmabuf_detach,
    .map_dma_buf    = my_dmabuf_map,          /* sg_table 반환 (DMA 주소) */
    .unmap_dma_buf  = my_dmabuf_unmap,
    .release        = my_dmabuf_release,      /* 버퍼 해제 */
    .mmap           = my_dmabuf_mmap,         /* 유저 공간 매핑 */
    .vmap           = my_dmabuf_vmap,         /* 커널 가상 주소 매핑 */
};

/* GEM → DMA-BUF export (PRIME) */
struct dma_buf *dmabuf = drm_gem_prime_export(gem_obj, flags);
/* → 유저 공간에서 fd = DRM_IOCTL_PRIME_HANDLE_TO_FD(handle) */

/* DMA-BUF → GEM import (PRIME) */
struct drm_gem_object *obj = drm_gem_prime_import(dev, dmabuf);
/* → 유저 공간에서 handle = DRM_IOCTL_PRIME_FD_TO_HANDLE(fd) */

dma_fence & dma_resv (동기화)

GPU 작업은 비동기로 실행되므로, 버퍼를 공유하는 디바이스들은 작업 완료를 동기화해야 합니다.

구조체역할사용 예
dma_fence GPU 작업 하나의 완료를 나타내는 동기화 프리미티브 GPU 커맨드 서브미션 후 fence 생성
dma_resv 버퍼별 fence 집합 관리 (reservation object) DMA-BUF에 내장. 읽기/쓰기 fence 추적
sync_file dma_fence를 유저 공간 fd로 노출 Explicit sync (Vulkan, Android)
/* Fence 기본 사용 패턴 */
struct dma_fence *fence;

/* GPU 작업 제출 시 fence 생성 */
fence = my_gpu_submit_job(job);

/* 버퍼의 reservation object에 fence 등록 */
dma_resv_add_fence(bo->base.resv, fence, DMA_RESV_USAGE_WRITE);

/* 다른 디바이스가 이 버퍼를 사용하기 전에 대기 */
ret = dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_WRITE,
                            true, msecs_to_jiffies(5000));

/* Fence 시그널 (GPU 인터럽트 핸들러에서 호출) */
dma_fence_signal(fence);
dma_fence_put(fence);

Implicit Sync vs Explicit Sync

GPU 동기화 모델은 두 가지로 나뉩니다. Linux DRM은 전통적으로 implicit sync를 사용했으나, Vulkan과 Android를 중심으로 explicit sync로 전환하고 있습니다.

항목Implicit Sync (암시적)Explicit Sync (명시적)
fence 관리 커널이 dma_resv에 자동 등록/확인 유저 공간이 sync_file fd로 직접 관리
KMS 인터페이스 커널이 프레임버퍼의 fence를 자동 대기 IN_FENCE_FD / OUT_FENCE_PTR plane property 사용
장점 유저 공간 구현 단순, 레거시 호환 세밀한 동기화 제어, GPU 파이프라인 최적화 가능
단점 불필요한 대기 발생 가능, 최적화 어려움 유저 공간 복잡도 증가
사용 API OpenGL (EGL), GBM Vulkan, Android HWC, Wayland explicit sync
커널 구성 dma_resv + 자동 fence 추적 sync_file + SYNC_IOC_MERGE / SYNC_IOC_FILE_INFO
/* Explicit Sync: KMS IN/OUT fence 사용 */

/* 유저 공간: GPU 렌더링 완료 fence fd를 Plane에 전달 */
drmModeAtomicAddProperty(req, plane_id, IN_FENCE_FD_prop, gpu_fence_fd);

/* 유저 공간: 커밋 완료 fence fd를 받을 위치 지정 */
int64_t out_fence_fd = -1;
drmModeAtomicAddProperty(req, crtc_id, OUT_FENCE_PTR_prop,
                         (uint64_t)(uintptr_t)&out_fence_fd);

/* Atomic 커밋 */
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_NONBLOCK |
                             DRM_MODE_PAGE_FLIP_EVENT, NULL);
/* out_fence_fd에 유효한 fd가 설정됨
 * → 다음 렌더링 시작 전에 이 fence를 GPU에 제출하여 대기
 * → 완전한 파이프라인 동기화 */
Wayland Explicit Sync: 최근 Wayland compositor와 클라이언트는 wp_linux_drm_syncobj_v1 계열 프로토콜로 DRM syncobj 기반 explicit sync를 협상할 수 있습니다. 핵심은 “커널 버전 숫자”보다 사용 중인 compositor, Mesa/driver, 커널 UAPI가 모두 syncobj timeline과 KMS fence를 연결할 수 있는지입니다. 즉, explicit sync는 render node 제출 경로와 KMS atomic commit 경로가 같은 fence 언어를 공유할 때 가장 큰 효과를 냅니다.

GPU 커맨드 서브미션

GPU 렌더링 작업은 커맨드 버퍼(command buffer)를 통해 제출됩니다. 유저 공간(Mesa/Vulkan 드라이버)이 GPU 명령어를 버퍼에 기록하고, 커널 드라이버가 이를 GPU 하드웨어에 전달합니다.

User Space Mesa GL/Vulkan 커맨드 버퍼 생성 GEM BO 할당 텍스처/버텍스/UBO Relocation/VA Map 주소 패칭 Submit ioctl 드라이버 고유 ioctl ioctl Kernel DRM Driver 권한/BO 검증 유효성 검사 dma_resv 락 의존성 fence 대기 drm_sched 등록 스케줄러 큐잉 run_job → HW 제출 링 버퍼/도어벨 기록 fence fd 반환 (explicit sync) 또는 dma_resv에 등록 (implicit) MMIO / Doorbell GPU Hardware Command Parser Shader Units Rasterizer ROP/Blender 완료 IRQ → fence signal

서브미션 모델 비교

모델드라이버ioctl특징
Ring Buffer i915 (레거시) I915_GEM_EXECBUFFER2 커맨드 버퍼를 링 버퍼에 복사. relocation으로 GPU 주소 패칭
Submit Queue amdgpu AMDGPU_CS IB(Indirect Buffer) 체인 제출. 다중 엔진(GFX/SDMA/UVD) 지원
Exec Queue xe (Intel) DRM_XE_EXEC VM-bind 기반. relocation 없음, GPU 페이지 테이블 직접 관리
Submitqueue msm (Qualcomm) MSM_GEM_SUBMIT 우선순위별 큐. faults 지원 (sparse binding)
Simple Submit panfrost, lima 드라이버별 drm_sched 기반. 단순 job 제출
/* amdgpu 커맨드 서브미션 예시 (유저 공간, libdrm_amdgpu) */
struct amdgpu_cs_request cs_req = {};
struct amdgpu_cs_ib_info ib_info = {
    .ib_mc_address = ib_gpu_addr,  /* IB의 GPU 가상 주소 */
    .size          = ib_size_dw,   /* 더블워드 단위 크기 */
};
cs_req.ip_type       = AMDGPU_HW_IP_GFX;   /* GFX 엔진 */
cs_req.number_of_ibs = 1;
cs_req.ibs           = &ib_info;

/* 의존 fence 목록 (이전 작업 완료 대기) */
struct amdgpu_cs_fence_info fence_info = { ... };
cs_req.fence_info = fence_info;

/* 서브미션 */
amdgpu_cs_submit(ctx, 0, &cs_req, 1);

/* 완료 대기 (선택적) */
struct amdgpu_cs_fence fence = {
    .context = ctx,
    .ip_type = AMDGPU_HW_IP_GFX,
    .fence   = cs_req.seq_no,
};
amdgpu_cs_query_fence_status(&fence, timeout_ns, 0, &expired);
설명 요약:
  • 커널 드라이버의 커맨드 서브미션 검증 (보안상 중요) */
  • 버퍼 오브젝트 유효성 */
  • 유저가 전달한 GEM 핸들이 실제 소유인지 확인 */
  • BO가 올바른 메모리 도메인에 있는지 확인 */
  • 커맨드 파싱 (일부 드라이버) */
  • 금지된 GPU 레지스터 접근 차단 */
  • 다른 프로세스의 GPU 주소 공간 접근 방지 */
  • 최신 GPU는 하드웨어 격리 (per-context page table)로 대체 */
  • 의존성 처리 */
  • 공유 BO의 dma_resv에서 기존 fence 확인 */
  • 의존 fence를 drm_sched_job의 dependency로 등록 */
  • 모든 의존 fence가 시그널된 후에만 GPU에 제출 */
  • fence 등록 */
  • 새 작업의 fence를 사용 BO의 dma_resv에 추가 */
  • explicit sync: fence를 sync_file fd로 유저에게 반환 */
VM-Bind vs Relocation: 레거시 드라이버(i915 execbuf)는 커맨드 버퍼 내 GPU 주소를 매번 패칭(relocation)했습니다. 현대 드라이버(xe, amdgpu VM)는 유저 공간이 GPU 페이지 테이블에 직접 매핑(VM-bind)하여 커맨드 버퍼가 고정 GPU 가상 주소를 사용합니다. VM-bind 모델은 relocation 오버헤드를 제거하고, sparse binding(일부 영역만 매핑)을 가능하게 합니다.

GPU 스케줄러 (drm_sched)

DRM GPU 스케줄러(drivers/gpu/drm/scheduler/)는 여러 프로세스의 GPU 작업을 공정하게 스케줄링하고, GPU 행(hang) 감지 및 복구를 처리합니다.

#include <drm/gpu_scheduler.h>

/* 스케줄러 초기화 (드라이버 probe에서) */
struct drm_gpu_scheduler sched;

drm_sched_init(&sched,
    &my_sched_ops,              /* 드라이버 콜백 */
    NULL,                       /* workqueue (NULL=기본) */
    num_hw_rings,               /* 하드웨어 큐 수 */
    0,                          /* credit limit */
    msecs_to_jiffies(10000),   /* 타임아웃 (10초) */
    NULL,                       /* workqueue for timeout */
    NULL,                       /* score */
    "my-gpu",                   /* 이름 */
    dev->dev);

/* 스케줄러 오퍼레이션 */
static const struct drm_sched_backend_ops my_sched_ops = {
    .run_job    = my_run_job,        /* GPU에 작업 제출 */
    .timedout_job = my_timedout_job, /* 타임아웃 시 GPU 리셋 */
    .free_job   = my_free_job,       /* 완료된 작업 해제 */
};

/* run_job 구현 예시 */
static struct dma_fence *my_run_job(struct drm_sched_job *job)
{
    struct my_job *mj = to_my_job(job);

    /* GPU 하드웨어 레지스터에 커맨드 버퍼 주소 기록 */
    writel(mj->cmd_buf_addr, gpu->regs + CMD_SUBMIT);

    /* 완료 fence 반환 (GPU 인터럽트에서 시그널) */
    return dma_fence_get(mj->fence);
}
구성 요소설명
drm_sched_entity클라이언트별 작업 큐. 유저 컨텍스트 하나가 entity 하나를 소유. 우선순위 지정 가능
drm_sched_jobGPU에 제출할 작업 하나. 의존 fence 목록 보유
drm_gpu_scheduler하나의 GPU 엔진(링 버퍼)을 관리. 라운드 로빈으로 entity에서 job 디큐
우선순위DRM_SCHED_PRIORITY_KERNEL > HIGH > NORMAL > LOW
타임아웃job이 지정 시간 내 완료 안 되면 timedout_job 콜백 → GPU 리셋
drm_sched 스케줄링 흐름 프로세스들 Entity A (유저) Entity B (유저) ... 우선순위 큐 KERNEL (최고) HIGH NORMAL (기본) LOW RR drm_gpu _scheduler 라운드 로빈 디큐 run_job() HW 커맨드 제출 GPU HW Ring Buffer 실행 엔진 완료 IRQ 핸들러 fence signal 다음 job Timeout! 시간 초과 timedout_job → 리셋 흐름: 프로세스 → Entity(우선순위별 큐) → Scheduler(라운드 로빈) → run_job() → GPU 실행 → IRQ → fence signal → 다음 job 디스패치
심화 학습: drm_sched의 Job 라이프사이클, dma_fence 동기화, TDR(Timeout Detection Recovery), i915 GuC vs amdgpu 구현 비교는 drm_sched (GPU Job 스케줄러) 페이지에서 자세히 다룹니다.

DRM 드라이버 기본 구조

최소한의 KMS DRM 드라이버 (디스플레이만 지원, GPU 가속 없음)의 골격입니다.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_fbdev_shmem.h>

struct my_drm {
    struct drm_device              drm;
    struct drm_simple_display_pipe pipe;
    struct drm_connector           connector;
    void __iomem                  *regs;
};

/* --- Connector --- */
static int my_connector_get_modes(struct drm_connector *conn)
{
    /* 고정 해상도 모드 추가 (실제로는 EDID 파싱) */
    return drm_add_modes_noedid(conn, 1920, 1080);
}

static const struct drm_connector_helper_funcs my_conn_helpers = {
    .get_modes = my_connector_get_modes,
};

/* --- Simple Display Pipe --- */
static void my_pipe_enable(struct drm_simple_display_pipe *pipe,
                           struct drm_crtc_state *crtc_state,
                           struct drm_plane_state *plane_state)
{
    struct my_drm *my = container_of(pipe, struct my_drm, pipe);
    struct drm_framebuffer *fb = plane_state->fb;

    /* 하드웨어 레지스터에 프레임버퍼 주소/포맷 설정 */
    writel(drm_fb_dma_get_gem_addr(fb, plane_state, 0),
           my->regs + FB_ADDR_REG);
    writel(fb->pitches[0], my->regs + FB_PITCH_REG);
    /* 디스플레이 엔진 활성화 */
    writel(1, my->regs + DISPLAY_ENABLE_REG);
}

static void my_pipe_update(struct drm_simple_display_pipe *pipe,
                           struct drm_plane_state *old_state)
{
    struct drm_plane_state *state = pipe->plane.state;
    if (state->fb)
        my_pipe_enable(pipe, NULL, state);
}

static const struct drm_simple_display_pipe_funcs my_pipe_funcs = {
    .enable  = my_pipe_enable,
    .update  = my_pipe_update,
};

/* --- DRM Driver --- */
DEFINE_DRM_GEM_FOPS(my_fops);

static const struct drm_driver my_driver = {
    .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
    .fops            = &my_fops,
    DRM_GEM_SHMEM_DRIVER_OPS,
    .name  = "my-display",
    .desc  = "My simple display driver",
    .date  = "20260101",
    .major = 1,
    .minor = 0,
};

/* --- Platform Driver --- */
static int my_probe(struct platform_device *pdev)
{
    struct my_drm *my;
    struct drm_device *drm;
    static const uint32_t formats[] = { DRM_FORMAT_XRGB8888 };
    int ret;

    my = devm_drm_dev_alloc(&pdev->dev, &my_driver,
                            struct my_drm, drm);
    if (IS_ERR(my))
        return PTR_ERR(my);
    drm = &my->drm;

    /* MMIO 매핑 */
    my->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(my->regs))
        return PTR_ERR(my->regs);

    /* Connector 초기화 */
    drm_connector_init(drm, &my->connector,
                       &my_conn_funcs, DRM_MODE_CONNECTOR_Unknown);
    drm_connector_helper_add(&my->connector, &my_conn_helpers);

    /* Simple display pipe 초기화 (CRTC+Plane+Encoder 한 번에) */
    ret = drm_simple_display_pipe_init(drm, &my->pipe,
            &my_pipe_funcs, formats, ARRAY_SIZE(formats),
            NULL, &my->connector);
    if (ret)
        return ret;

    drm_mode_config_reset(drm);
    ret = drm_dev_register(drm, 0);
    if (ret)
        return ret;

    /* fbdev 에뮬레이션 (콘솔 출력) */
    drm_fbdev_shmem_setup(drm, 32);
    return 0;
}

MODULE_LICENSE("GPL");
drm_simple_display_pipe: 간단한 디스플레이 전용 드라이버(CRTC 1개, Plane 1개, Encoder 1개)에 적합한 헬퍼입니다. 복잡한 다중 CRTC/Plane 구성이 필요하면 각 KMS 오브젝트를 개별적으로 초기화해야 합니다.

주요 오픈소스 DRM 드라이버

드라이버하드웨어커널 경로메모리 관리특징
i915 Intel 내장 GPU drivers/gpu/drm/i915/ GEM + GTT/GGTT GuC/HuC 펌웨어 지원, Display 13/14, Xe 드라이버로 전환 중
xe Intel Xe GPU (DG2+) drivers/gpu/drm/xe/ TTM 기반 i915 후속. 디스크리트 GPU(Arc) 지원, drm_sched 사용
amdgpu AMD Radeon (GCN+) drivers/gpu/drm/amd/ TTM DC (Display Core), KFD (HSA 컴퓨트), SR-IOV, RAS
nouveau NVIDIA (리버스 엔지니어링) drivers/gpu/drm/nouveau/ TTM 커뮤니티 개발, 최신 GPU 지원 제한적, GSP 펌웨어 지원
panfrost ARM Mali (Midgard/Bifrost) drivers/gpu/drm/panfrost/ GEM shmem OpenGL ES 지원, drm_sched 사용, MMU 자체 관리
lima ARM Mali (Utgard) drivers/gpu/drm/lima/ GEM shmem 레거시 ARM Mali-400/450, OpenGL ES 2.0
vc4 Broadcom VideoCore IV drivers/gpu/drm/vc4/ GEM CMA Raspberry Pi 0~3. v3d는 Pi 4/5용 별도 드라이버
msm Qualcomm Adreno drivers/gpu/drm/msm/ GEM Freedreno 스택, submitqueue 기반 커맨드 제출
virtio-gpu 가상 GPU (QEMU/virgl) drivers/gpu/drm/virtio/ GEM shmem VM 내 3D 가속, virglrenderer로 호스트 GPU 사용
simpledrm EFI/BIOS 프레임버퍼 drivers/gpu/drm/tiny/simpledrm.c GEM shmem 부팅 초기 콘솔. UEFI GOP/VBE 프레임버퍼 위에서 동작
nouveau 참고: NVIDIA는 공식 오픈소스 커널 드라이버를 별도로 제공합니다 (Turing 이후, nvidia-open). nouveau는 커뮤니티 리버스 엔지니어링 기반으로, 최신 GPU에서는 전력 관리(reclocking)와 성능이 제한됩니다. 최근 커널에서는 GSP(GPU System Processor) 펌웨어를 통해 nouveau의 기능이 개선되고 있습니다.

최근 DRM 코어의 방향

버전 번호 자체보다 중요한 것은 메인라인이 어떤 설계 축으로 이동하는가입니다. 최근 DRM 문서와 헤더를 보면 다음 네 가지 흐름이 거의 모든 드라이버 설계에 공통으로 반영됩니다.

최근 방향실무 의미
권한 분리 primary/render/accel 노드 역할이 더 명확해짐 display server, 일반 앱, AI runtime이 같은 fd 모델을 공유하지 않도록 설계해야 합니다.
상태 객체 확장 atomic property가 색 관리, writeback, HDR/VRR, content protection까지 포괄 새 기능은 대개 ioctl 추가보다 property/state 추가로 들어옵니다.
동기화 명시화 dma_resv, syncobj, explicit sync, timeline fence 사용이 확대 render 완료와 KMS 표시 완료를 같은 fence 체인으로 연결하는 능력이 중요해집니다.
복구/관측성 drm_sched timeout, reset recovery, drm_panic, tracepoint 강화 좋은 드라이버는 빠르기만 한 것이 아니라 hang 후에도 원인과 복구 경로를 설명할 수 있어야 합니다.
drm_panic의 위치: GPU가 패닉 화면을 직접 그리는 기능은 “최근 흐름”의 일부입니다. 하지만 이것은 특정 드라이버가 패닉 시점에도 안전하게 최소 렌더 경로를 유지할 수 있어야 가능한 기능이므로, 일반적인 fast path 설계와는 분리해서 생각해야 합니다.

DRM Bridge & Panel 프레임워크

임베디드/모바일 SoC에서는 디스플레이 출력이 여러 개의 하드웨어 블록 체인으로 구성됩니다. drm_bridge는 이 체인의 중간 단계(MIPI-DSI to HDMI 변환기, LVDS 직렬화기 등)를, drm_panel은 최종 디스플레이 패널을 추상화합니다.

Encoder SoC 디스플레이 출력 Bridge #1 예: DSI→HDMI (lt9611, sii902x 등) Bridge #2 예: LVDS 직렬화기 (sn65dsi86 등) Panel drm_panel (또는 Connector) LCD 각 Bridge는 pre_enable → enable → disable → post_disable 콜백을 체인 순서로 실행 DRM 코어가 체인 순회를 자동 관리 (atomic helper)
/* DRM Bridge 드라이버 구현 예시 */
#include <drm/drm_bridge.h>

static const struct drm_bridge_funcs my_bridge_funcs = {
    /* 디스플레이 파이프라인 활성화 순서 */
    .pre_enable  = my_bridge_pre_enable,  /* PLL/클럭 설정 */
    .enable      = my_bridge_enable,      /* 출력 활성화 */
    .disable     = my_bridge_disable,
    .post_disable = my_bridge_post_disable,

    /* 모드 검증 */
    .mode_valid  = my_bridge_mode_valid,  /* 지원 해상도 필터링 */
    .mode_fixup  = my_bridge_mode_fixup,  /* adjusted_mode 수정 */

    /* Atomic 지원 (권장) */
    .atomic_pre_enable = my_bridge_atomic_pre_enable,
    .atomic_enable     = my_bridge_atomic_enable,
    .atomic_disable    = my_bridge_atomic_disable,

    /* 출력 감지 (DRM_BRIDGE_OP_DETECT) */
    .detect      = my_bridge_detect,
    .get_modes   = my_bridge_get_modes,   /* EDID 기반 모드 목록 */
    .get_edid    = my_bridge_get_edid,    /* EDID 읽기 */
};

static int my_bridge_probe(struct i2c_client *client)
{
    struct my_bridge *mb = devm_kzalloc(&client->dev, sizeof(*mb), GFP_KERNEL);

    mb->bridge.funcs = &my_bridge_funcs;
    mb->bridge.of_node = client->dev.of_node;
    mb->bridge.type = DRM_MODE_CONNECTOR_HDMIA;  /* 출력 커넥터 타입 */
    mb->bridge.ops = DRM_BRIDGE_OP_DETECT |
                     DRM_BRIDGE_OP_EDID |
                     DRM_BRIDGE_OP_MODES;

    drm_bridge_add(&mb->bridge);
    return 0;
}
/* DRM Panel 드라이버 구현 예시 */
#include <drm/drm_panel.h>

static const struct drm_panel_funcs my_panel_funcs = {
    .prepare    = my_panel_prepare,     /* 전원 ON, 리셋 해제 */
    .enable     = my_panel_enable,      /* 백라이트 ON, 디스플레이 ON */
    .disable    = my_panel_disable,     /* 백라이트 OFF */
    .unprepare  = my_panel_unprepare,   /* 전원 OFF */
    .get_modes  = my_panel_get_modes,   /* 고정 모드 반환 */
};

static int my_panel_probe(struct mipi_dsi_device *dsi)
{
    struct my_panel *panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);

    drm_panel_init(&panel->panel, &dsi->dev, &my_panel_funcs,
                   DRM_MODE_CONNECTOR_DSI);
    drm_panel_add(&panel->panel);
    return 0;
}

/* 디스플레이 드라이버에서 bridge + panel 연결 */
struct drm_bridge *bridge;
bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
/* drm_panel은 자동으로 panel-bridge로 래핑됨 */
drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);

/* 체인의 마지막 bridge에서 connector 생성 */
connector = drm_bridge_connector_init(drm, encoder);
Device Tree 바인딩: Bridge와 Panel은 Device Tree의 ports/port/endpoint 노드로 연결됩니다. of_graph_get_remote_port_parent()로 연결된 디바이스를 탐색하고, devm_drm_of_get_bridge()가 DT 그래프를 따라 bridge 또는 panel-bridge를 자동으로 찾습니다.

GPU 가상 메모리 (GPUVM)

현대 GPU는 자체 MMU를 가지며, 프로세스별 독립적인 GPU 가상 주소 공간을 제공합니다. 커널 DRM 프레임워크는 drm_gpuvm(drivers/gpu/drm/drm_gpuvm.c)으로 이를 추상화합니다.

GPUVM: 프로세스별 GPU 가상 주소 공간 Process A GPU VA 0x0001_0000_0000 → Shader Code 0x0002_0000_0000 → Vertex Buffer 0x0003_0000_0000 → Uniform Buffer ... 미매핑 영역 다수 ... 프로세스 A 전용 매핑 테이블 Process B GPU VA 0x0001_0000_0000 → Shader Code 0x0002_0000_0000 → Texture Atlas 0x0004_0000_0000 → Compute Buffer ... 다른 프로세스와 독립 ... 프로세스 B 전용 매핑 테이블 GPU MMU + Page Table Walk GPU VA → 물리 주소 변환 (per-context) TLB 캐시, 권한 검사, fault 처리 VRAM (Local Memory) 고대역폭 그래픽 메모리 렌더 타깃 / 텍스처 / 캐시 라인 디바이스 로컬 우선 배치 System Memory (GEM/TTM) 공유 버퍼 / pageable backing IOMMU 경유 DMA 접근 메모리 압박 시 migration `drm_gpuvm`: VA 범위(`mm_start`, `mm_range`) + RB 트리(`rb_root_cached`)로 매핑 관리 `drm_gpuva`: `va_start`, `va_range`, `gem_obj`, `gem_offset`로 개별 매핑 표현
IOMMU vs GPU MMU: IOMMU(VT-d/SMMU)는 디바이스의 DMA 주소를 물리 주소로 변환하는 시스템 레벨 하드웨어입니다. GPU MMU는 GPU 내부의 셰이더/엔진이 사용하는 가상 주소를 물리 주소로 변환합니다. 두 계층은 독립적으로 동작하며, GPU DMA 트래픽은 IOMMU도 통과합니다.
심화 학습: CPU와 GPU가 동일한 가상 주소 공간을 공유하는 HMM(Heterogeneous Memory Management), migrate_vma(), ZONE_DEVICE, SVM(ROCm KFD) 구현은 HMM (이기종 메모리 관리) 페이지에서 자세히 다룹니다.

GPU 전원 관리

GPU는 시스템에서 가장 전력을 많이 소비하는 디바이스 중 하나이므로, 정교한 전원 관리가 매우 중요합니다.

메커니즘설명커널 인터페이스
Runtime PM GPU 유휴 시 자동 절전. D3cold까지 진입 가능 pm_runtime_get_sync() / pm_runtime_put_autosuspend()
DVFS Dynamic Voltage and Frequency Scaling. 부하에 따라 클럭/전압 조절 devfreq 프레임워크 또는 드라이버 자체 구현
Power Gating 미사용 GPU 블록(셰이더 유닛 등)의 전원을 완전히 차단 드라이버별 구현 (HW 의존)
Clock Gating 미사용 블록의 클럭만 차단 (power gating보다 빠른 복구) 드라이버별 구현
Hybrid GPU 내장/외장 GPU 전환 (PRIME offload, reverse PRIME) DRI_PRIME=1 환경 변수, switcheroo sysfs
# GPU Runtime PM 상태 확인
cat /sys/class/drm/card0/device/power/runtime_status
# active / suspended / suspending

# Runtime PM 자동 절전 지연 설정 (밀리초)
echo 5000 > /sys/class/drm/card0/device/power/autosuspend_delay_ms

# GPU 클럭 주파수 확인 (amdgpu 예시)
cat /sys/class/drm/card0/device/pp_dpm_sclk
# 0: 300Mhz
# 1: 600Mhz *
# 2: 900Mhz

# PRIME GPU 오프로드 (외장 GPU로 렌더링)
DRI_PRIME=1 glxinfo | grep "OpenGL renderer"

# vga_switcheroo 상태 (하이브리드 GPU)
cat /sys/kernel/debug/vgaswitcheroo/switch
GPU 전원 상태 전환 Active (D0) DVFS: 고성능 (900MHz/1.1V) DVFS: 중간 (600MHz/0.9V) DVFS: 저전력 (300MHz) devfreq / 드라이버 Clock Gating 미사용 블록 클럭 차단 유휴 Power Gating 미사용 블록 전원 차단 더 깊은 절전 D3hot Runtime Suspend PCIe 링크 유지 전체 유휴 D3cold 전원 완전 차단 PCIe 링크 끊김 PCIe 재연결 pm_runtime_get_sync() → 복귀 Hybrid GPU (PRIME Offload) DRI_PRIME=1 → 외장 GPU 활성화 → 렌더링 → 복사 → 내장 GPU 출력 자동 절전 (autosuspend) autosuspend_delay_ms 경과 후 자동 D3 진입 활성 경로 절전 경로 복귀 경로 전력 소비: Active(D0) > Clock Gate > Power Gate > D3hot > D3cold(최저)

GPU 리셋 및 복구

GPU는 잘못된 셰이더, 무한 루프, 하드웨어 결함 등으로 응답 불능(hang) 상태에 빠질 수 있습니다. DRM 프레임워크는 hang 감지, GPU 리셋, 작업 재제출의 3단계 복구 메커니즘을 제공합니다.

Hang 감지

메커니즘설명구현
스케줄러 타임아웃 drm_sched이 job별 타이머 설정. 만료 시 timedout_job 콜백 대부분의 현대 드라이버
하드웨어 워치독 GPU 내부 워치독 타이머가 hang 감지 후 인터럽트 발생 amdgpu (UVD/VCE), 일부 ARM GPU
Heartbeat 주기적으로 GPU에 nop 작업 제출 후 완료 확인 i915 (engine heartbeat)
Seqno 모니터링 GPU가 작업 완료 시 증가시키는 시퀀스 번호를 주기적으로 확인 레거시 드라이버

리셋 수준

/* GPU 리셋 수준 (세밀한 것부터 거친 순서) */

/* Level 1: Per-engine 리셋 (권장, 최소 영향) */
/* 특정 GPU 엔진(GFX, SDMA, 비디오 등)만 리셋 */
/* 다른 엔진은 계속 동작 */
amdgpu_device_reset_engine(adev, ring);  /* amdgpu 예시 */

/* Level 2: Per-context 리셋 (하드웨어 지원 필요) */
/* 특정 GPU 컨텍스트만 무효화 */
/* i915: 잘못된 컨텍스트를 ban (금지) 처리 */
intel_context_ban(ce, NULL);

/* Level 3: 전체 GPU 리셋 */
/* 모든 엔진/컨텍스트 중단 후 GPU 완전 재초기화 */
amdgpu_device_gpu_recover(adev, NULL, false);

/* Level 4: FLR (Function Level Reset) - PCIe */
/* PCIe 레벨에서 디바이스 전체 리셋 */
pci_reset_function(pdev);

/* Level 5: BACO (Bus Active, Chip Off) - AMD */
/* GPU 전원을 완전히 차단 후 재투입 */
amdgpu_device_baco_enter(adev_to_drm(adev));
amdgpu_device_baco_exit(adev_to_drm(adev));
GPU 리셋 수준 (영향 범위 증가 →) 최소 영향 최대 영향 Level 1 Per-engine 특정 엔진만 리셋 (GFX/SDMA/Video) 다른 엔진 영향 없음 Level 2 Per-context 문제 컨텍스트만 ban (HW 지원 필요) i915: context ban Level 3 Full GPU Reset 전체 GPU 재초기화 모든 엔진/컨텍스트 중단 amdgpu_device_gpu_recover Level 4 FLR (PCIe) PCIe Function Level Reset 수행 pci_reset_function Level 5 BACO Bus Active, Chip Off GPU 전원 완전 차단/재투입 AMD 전용 리셋 전략: 1. 항상 가장 세밀한 수준(Level 1)부터 시도하여 다른 작업에 미치는 영향을 최소화합니다. 2. 실패 시 더 거친 수준으로 단계적 확대(escalation)합니다. drm_sched의 timedout_job 콜백에서 이 흐름을 구현합니다. 3. Level 5(BACO)는 최후의 수단으로, GPU 전원을 물리적으로 차단했다가 다시 투입하여 하드웨어 상태를 완전히 초기화합니다.

복구 흐름

/* drm_sched 타임아웃 → 복구 흐름 */

/* 1. 타임아웃 콜백 호출 */
static enum drm_gpu_sched_stat
my_timedout_job(struct drm_sched_job *job)
{
    struct my_device *dev = job_to_dev(job);

    /* 2. GPU 상태 덤프 (디버깅용) */
    dev_coredump_snapshot(dev);  /* devcoredump 프레임워크 */

    /* 3. GPU 리셋 수행 */
    my_gpu_reset(dev);

    /* 4. 스케줄러에 리셋 완료 통보 */
    /*    → 대기 중인 fence들을 에러로 시그널 */
    /*    → guilty 작업의 entity에 guilty 플래그 설정 */
    return DRM_GPU_SCHED_STAT_NOMINAL;
}

/* 5. 유저 공간 알림 */
/*    - 리셋 후 fence가 에러(-EIO)로 시그널됨 */
/*    - Vulkan: VK_ERROR_DEVICE_LOST 반환 */
/*    - GL: GL_CONTEXT_LOST 또는 glGetGraphicsResetStatusARB() */
/*    - amdgpu: AMDGPU_CTX_QUERY2 ioctl로 리셋 감지 */
GPU 리셋 영향:
  • VRAM 콘텐츠: 전체 리셋 시 VRAM 내용이 소실될 수 있음. TTM이 시스템 메모리에 백업한 BO만 복구 가능
  • 디스플레이: GPU 리셋 중 화면이 일시적으로 검은색으로 전환될 수 있음 (modeset 재설정 필요)
  • 멀티 GPU: SR-IOV 환경에서는 VF(Virtual Function) 리셋이 다른 VF에 영향을 줄 수 있음
  • RAS (Reliability): amdgpu는 ras_controller로 ECC 에러 감지 후 수정 불가 에러 시 자동 리셋
# GPU 리셋 관련 sysfs/debugfs

# amdgpu: 수동 GPU 복구 트리거
echo 1 > /sys/kernel/debug/dri/0/amdgpu_gpu_recover

# i915: 엔진 리셋 카운트
cat /sys/kernel/debug/dri/0/i915_reset_info

# devcoredump: GPU 상태 덤프 읽기
cat /sys/class/devcoredump/devcd0/data > gpu_dump.bin

# devcoredump 삭제 (5분 후 자동 삭제)
echo 1 > /sys/class/devcoredump/devcd0/data

# dmesg에서 GPU 리셋 로그 확인
dmesg | grep -i "gpu\|reset\|hang\|timed out"

커널 설정 (Kconfig)

# DRM 핵심 설정
CONFIG_DRM=m                      # DRM 코어 모듈
CONFIG_DRM_KMS_HELPER=m           # KMS 헬퍼 함수
CONFIG_DRM_GEM_SHMEM_HELPER=m     # GEM shmem 헬퍼
CONFIG_DRM_SCHED=m                # GPU 스케줄러
CONFIG_DRM_TTM=m                  # TTM 메모리 관리자
CONFIG_DRM_DISPLAY_HELPER=m       # DP/HDMI 디스플레이 헬퍼

# 드라이버별 설정
CONFIG_DRM_I915=m                 # Intel i915
CONFIG_DRM_XE=m                   # Intel Xe
CONFIG_DRM_AMDGPU=m               # AMD GPU
CONFIG_DRM_NOUVEAU=m              # NVIDIA (오픈소스)
CONFIG_DRM_PANFROST=m             # ARM Mali (Midgard/Bifrost)
CONFIG_DRM_LIMA=m                 # ARM Mali (Utgard)
CONFIG_DRM_VC4=m                  # Broadcom VideoCore
CONFIG_DRM_MSM=m                  # Qualcomm Adreno
CONFIG_DRM_VIRTIO_GPU=m           # 가상 GPU (QEMU)
CONFIG_DRM_SIMPLEDRM=y            # EFI/BIOS 프레임버퍼

# fbdev 에뮬레이션 (DRM 위에서 /dev/fb0 제공)
CONFIG_DRM_FBDEV_EMULATION=y

# DMA-BUF
CONFIG_DMA_SHARED_BUFFER=y

DRM 디버깅

디버깅 도구

도구용도사용법
modetest KMS 오브젝트 나열, 모드 테스트 modetest -M i915 (libdrm 제공)
drm_info DRM 디바이스 전체 정보 출력 (JSON) drm_info
intel_gpu_top Intel GPU 엔진별 사용률 모니터링 intel_gpu_top (intel-gpu-tools)
umr AMD GPU 레지스터 읽기/디코딩 umr -O bits -r *.mmMC_VM_FB_LOCATION_BASE
weston-debug Wayland compositor 디버깅 Weston의 DRM backend 로그
debugfs DRM 내부 상태 확인 /sys/kernel/debug/dri/0/

커널 디버그 메시지

# DRM 디버그 레벨 설정 (비트마스크)
echo 0x1ff > /sys/module/drm/parameters/debug

# 비트마스크 의미:
#   0x001 = DRM_UT_CORE     — DRM 코어
#   0x002 = DRM_UT_DRIVER   — 드라이버
#   0x004 = DRM_UT_KMS      — 모드 설정
#   0x008 = DRM_UT_PRIME    — PRIME/DMA-BUF
#   0x010 = DRM_UT_ATOMIC   — Atomic modesetting
#   0x020 = DRM_UT_VBL      — Vblank
#   0x040 = DRM_UT_STATE    — Atomic 상태
#   0x080 = DRM_UT_LEASE    — DRM 리스
#   0x100 = DRM_UT_DP       — DisplayPort
#   0x200 = DRM_UT_DRMRES   — 리소스 관리

# 부팅 시 커널 파라미터로 설정
# drm.debug=0x1e (DRIVER+KMS+PRIME+ATOMIC)

# DRM debugfs 정보 확인
ls /sys/kernel/debug/dri/0/
# clients  gem_names  name  state  ...

# GEM 객체 목록 (메모리 사용량 확인)
cat /sys/kernel/debug/dri/0/gem_names

# 현재 KMS 상태 (모든 CRTC/Plane/Connector)
cat /sys/kernel/debug/dri/0/state

# GPU 행(hang) 시 ftrace로 추적
echo 1 > /sys/kernel/debug/tracing/events/drm/enable
cat /sys/kernel/debug/tracing/trace_pipe
GPU 행(hang) 디버깅:
  • dmesg에서 "GPU HANG" 또는 "timed out" 메시지 확인
  • i915: /sys/kernel/debug/dri/0/i915_gpu_info에서 엔진 상태 덤프
  • amdgpu: /sys/kernel/debug/dri/0/amdgpu_gpu_recover로 수동 GPU 리셋
  • GPU coredump: 일부 드라이버는 devcoredump 프레임워크로 GPU 상태 덤프 생성 (/sys/class/devcoredump/)

GPU 성능 프로파일링

도구대상 GPU측정 항목사용 예
intel_gpu_top Intel 엔진 사용률, 주파수, 전력 intel_gpu_top -l
radeontop AMD 파이프라인 사용률, VRAM 사용량 radeontop -d -
nvtop AMD/Intel/NVIDIA 프로세스별 GPU 사용률 (htop 스타일) nvtop
gpu_metrics AMD (sysfs) 온도, 전력, 주파수, 팬 속도 cat /sys/class/drm/card0/device/gpu_metrics
i915 perf Intel EU 효율, 메모리 대역폭, 캐시 히트율 intel_gpu_frequency -g
ftrace 모든 DRM drm_vblank, atomic_commit 이벤트 추적 trace-cmd record -e drm
# 프레임 드롭 디버깅 — vblank 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_vblank_event/enable
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_vblank_event_delivered/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 출력: vblank 시퀀스 번호와 타임스탬프 → 누락된 프레임 확인

# Atomic commit 성능 추적
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_atomic_state/enable
# commit 소요 시간, 포함된 CRTC/Plane 수 확인

# GPU 메모리 사용량 상세 (드라이버별 debugfs)
# amdgpu:
cat /sys/kernel/debug/dri/0/amdgpu_gem_info       # 프로세스별 GEM 사용량
cat /sys/kernel/debug/dri/0/amdgpu_vram_mm        # VRAM 할당 맵
cat /sys/kernel/debug/dri/0/amdgpu_vm_info        # GPU 가상 메모리 통계

# i915:
cat /sys/kernel/debug/dri/0/i915_gem_objects      # GEM 객체 통계
cat /sys/kernel/debug/dri/0/i915_frequency_info   # GPU 주파수 정보

DRM ioctl 요약

ioctl용도필요 권한
DRM_IOCTL_VERSION드라이버 이름/버전 조회없음
DRM_IOCTL_GET_CAP기능 조회 (PRIME, ATOMIC 등)없음
DRM_IOCTL_MODE_GETRESOURCESCRTC/Connector/Encoder ID 목록없음
DRM_IOCTL_MODE_GETCONNECTORConnector 상태, 지원 모드 조회없음
DRM_IOCTL_MODE_GETENCODEREncoder 정보 조회없음
DRM_IOCTL_MODE_GETCRTCCRTC 현재 모드 조회없음
DRM_IOCTL_MODE_SETCRTC모드 설정 (레거시)DRM Master
DRM_IOCTL_MODE_ATOMICAtomic 모드 설정/페이지 플립DRM Master
DRM_IOCTL_MODE_CREATE_DUMBDumb 버퍼 생성없음
DRM_IOCTL_MODE_MAP_DUMBDumb 버퍼 mmap 오프셋없음
DRM_IOCTL_MODE_DESTROY_DUMBDumb 버퍼 해제없음
DRM_IOCTL_PRIME_HANDLE_TO_FDGEM → DMA-BUF fd없음
DRM_IOCTL_PRIME_FD_TO_HANDLEDMA-BUF fd → GEM없음
DRM_IOCTL_GEM_CLOSEGEM 핸들 해제없음
DRM_IOCTL_MODE_ADDFB2프레임버퍼 객체 생성없음
DRM_IOCTL_MODE_PAGE_FLIP페이지 플립 (레거시)DRM Master
DRM_IOCTL_SET_CLIENT_CAP클라이언트 기능 활성화 (ATOMIC 등)DRM Master

DRM Lease (디스플레이 리스)

DRM Lease는 DRM Master가 자신의 KMS 리소스(CRTC, Connector, Plane) 일부를 다른 프로세스에게 독점적으로 위임하는 메커니즘입니다. VR 헤드셋, 멀티시트 디스플레이, 게임 전용 출력 등에서 사용됩니다.

DRM Master (Wayland Compositor) CRTC 0,1 / Conn 0,1 Plane 0,1,2,3 Lease #1 Lease #2 Lessee #1 (VR) CRTC 1 + Conn 1 (HMD) 독립적인 DRM Master 권한 Lessee #2 CRTC 0 + Conn 0 별도 디스플레이 관리 VR 헤드셋 메인 모니터
ioctl설명호출자
DRM_IOCTL_MODE_CREATE_LEASE KMS 오브젝트 집합을 리스로 생성, lessee fd 반환 DRM Master (Lessor)
DRM_IOCTL_MODE_LIST_LESSEES 현재 활성 lessee 목록 조회 DRM Master
DRM_IOCTL_MODE_GET_LEASE 리스에 포함된 오브젝트 ID 목록 조회 Lessee
DRM_IOCTL_MODE_REVOKE_LEASE 리스 취소 (오브젝트 회수) DRM Master
/* DRM Lease 생성 (유저 공간, libdrm) */
uint32_t objects[] = {
    crtc_id,       /* 리스할 CRTC */
    connector_id,  /* 리스할 Connector */
    plane_id,      /* 리스할 Plane */
};

struct drm_mode_create_lease lease = {
    .object_ids   = (uint64_t)(uintptr_t)objects,
    .object_count = 3,
    .flags        = O_CLOEXEC | O_NONBLOCK,
};

drmIoctl(master_fd, DRM_IOCTL_MODE_CREATE_LEASE, &lease);
/* lease.lessee_id = 리스 ID */
/* lease.fd = lessee가 사용할 새 DRM fd */

/* Lessee는 lease.fd를 통해 리스된 오브젝트만 접근 가능 */
/* → 독립적인 atomic commit, 페이지 플립 수행 */
VR 활용: OpenXR 런타임(Monado 등)이 compositor로부터 VR 헤드셋 출력을 lease로 받아 직접 제어합니다. 이렇게 하면 compositor를 거치지 않고 HMD에 직접 프레임을 전달하여 latency를 최소화할 수 있습니다. Wayland에서는 wp_drm_lease_device_v1 프로토콜로 compositor가 lease를 제공합니다.

Content Protection (HDCP)

HDCP(High-bandwidth Digital Content Protection)는 디스플레이 출력의 콘텐츠를 암호화하여 무단 복제를 방지하는 DRM(Digital Rights Management) 기술입니다. Linux DRM 서브시스템은 KMS property를 통해 HDCP를 지원합니다.

HDCP 버전대역폭사용 인터페이스특징
HDCP 1.4 TMDS (HDMI 1.x/DP) HDMI, DP, DVI 기본 암호화, 리피터 인증
HDCP 2.2 HDMI 2.0+ HDMI 2.x 향상된 암호화(AES-128), LC 검증
HDCP 2.3 HDMI 2.1/DP 2.x HDMI 2.1, DP 2.x 최신 보안 개선
/* HDCP KMS Properties (Connector) */

/* "Content Protection" property */
/* 값: Undesired (0) → Desired (1) → Enabled (2) */
/*     유저가 Desired로 설정하면 드라이버가 HDCP 인증 시작 */
/*     성공 시 Enabled로 전환 (읽기 전용) */

/* "HDCP Content Type" property */
/* 값: Type 0 — HDCP 1.4+ 허용 */
/*     Type 1 — HDCP 2.2+ 필수 (4K UHD 콘텐츠) */

/* Connector에 HDCP property 등록 (드라이버) */
drm_connector_attach_content_protection_property(connector, true);
/* true = HDCP Content Type property도 함께 등록 */

/* HDCP 인증 흐름 (드라이버 내부) */
/* 1. 유저가 "Content Protection" = Desired 설정 */
/* 2. atomic_check에서 HDCP 요청 감지 */
/* 3. 드라이버가 수신기(모니터)와 HDCP 핸드셰이크 */
/*    - AKE_Init / AKE_Send_Cert / AKE_Send_Km ... */
/* 4. 인증 성공 → "Content Protection" = Enabled */
/* 5. 링크 실패/핫플러그 → "Content Protection" = Desired */
/*    → 유저 공간에 uevent 통보 → 재인증 시도 */
HDCP 인증 핸드셰이크 (HDCP 2.x) Transmitter (GPU) Receiver (모니터) 1. AKE_Init 2. AKE_Send_Cert 3. AKE_Send_Km 4. LC_Init 5. LC_Send_L' 6. SKE_Send_Eks 7. Encrypted Content (암호화된 콘텐츠 전송) 세션 시작 인증서 전송 마스터 키 로컬리티 검증 로컬리티 응답 세션 키 교환 AKE LC SKE AKE = Authentication and Key Exchange | LC = Locality Check | SKE = Session Key Exchange
HDCP 요구사항: HDCP는 하드웨어 지원이 필수입니다. GPU의 디스플레이 엔진이 HDCP 암호화를 지원해야 하며, 모니터/TV도 해당 HDCP 버전을 지원해야 합니다. Intel GPU(i915)와 AMD GPU(amdgpu DC)가 Linux에서 HDCP를 지원하는 주요 드라이버입니다. HDCP 키는 GPU 펌웨어 또는 NVRAM에 저장되며, 커널은 키 자체를 직접 관리하지 않습니다.

디스플레이 연결 프로토콜

프로토콜DRM Connector 타입최대 대역폭특징
HDMI 2.1 DRM_MODE_CONNECTOR_HDMIA/B 48 Gbps ARC/eARC, VRR, DSC, 4K@120Hz
DisplayPort 2.1 DRM_MODE_CONNECTOR_DisplayPort 80 Gbps (UHBR20) MST (데이지체인), DSC, Adaptive-Sync
eDP DRM_MODE_CONNECTOR_eDP DP 기반 내장 디스플레이 (노트북), PSR (Panel Self Refresh)
DSI (MIPI) DRM_MODE_CONNECTOR_DSI 레인당 ~4.5 Gbps 모바일/임베디드 디스플레이, 커맨드/비디오 모드
DPI (RGB) DRM_MODE_CONNECTOR_DPI 병렬 전송 패럴렐 RGB, 임베디드 LCD 직결
LVDS DRM_MODE_CONNECTOR_LVDS 채널당 ~112 MHz 레거시 노트북/산업용 패널
VGA DRM_MODE_CONNECTOR_VGA ~400 MHz (아날로그) 레거시 아날로그, DAC 필요
USB-C (DP Alt) DRM_MODE_CONNECTOR_USB DP 기반 USB Type-C를 통한 DP 출력, typec_mux 드라이버

DisplayPort MST (Multi-Stream Transport)

DP MST는 하나의 DP 출력에서 여러 디스플레이를 데이지 체인으로 연결하거나 MST 허브를 통해 분기하는 기술입니다. 커널은 drm_dp_mst_topology_mgr로 MST 토폴로지를 관리합니다.

구성 요소커널 구조체역할
Topology Manager drm_dp_mst_topology_mgr MST 허브/브랜치 장치 탐색, 대역폭 할당, 핫플러그 처리
MST Port drm_dp_mst_port 브랜치 장치의 각 출력 포트 (downstream connector)
Payload drm_dp_mst_atomic_payload 각 스트림의 대역폭 할당 (시간 슬롯)
VCPI (Virtual Channel Payload ID) Atomic state 내장 가상 채널별 대역폭 크기 (시간 슬롯 수)
/* MST 토폴로지 매니저 초기화 (드라이버) */
drm_dp_mst_topology_mgr_init(&my->mst_mgr, &my->aux,
    16,   /* 최대 페이로드 수 */
    4,    /* 최대 레인 수 */
    conn_base_id);

/* MST 허브 감지 시 (HPD IRQ에서) */
drm_dp_mst_hpd_irq_handle_event(&my->mst_mgr, esi);
/* → 토폴로지 변경 시 connector 추가/제거 이벤트 */

/* MST 대역폭 계산 예시 */
/* DP 1.4 HBR3: 8.1 Gbps/레인 × 4 레인 = 32.4 Gbps 총 대역폭 */
/* 4K@60Hz (XRGB8888): ~12.5 Gbps 필요 → 63개 시간 슬롯 중 ~25개 사용 */
/* 나머지 슬롯으로 2번째 모니터 (FHD@60Hz) 연결 가능 */

DSC (Display Stream Compression)

DSC는 VESA가 표준화한 시각적 무손실(visually lossless) 디스플레이 압축입니다. 고해상도(8K, 4K@120Hz+)에서 DP/HDMI 대역폭 한계를 극복하기 위해 사용됩니다. Linux DRM에서는 drm_dsc_config로 DSC 파라미터를 관리합니다.

항목설명
압축률일반적으로 3:1 (24bpp → 8bpp), 최대 4:1 가능
슬라이스프레임을 수평 슬라이스로 분할하여 병렬 압축/복원. 슬라이스 수는 모니터 DSC 능력에 의존
커널 헬퍼drm_dsc_compute_rc_parameters()로 Rate Control 파라미터 자동 계산
협상Source(GPU)와 Sink(모니터) 양쪽이 DSC 지원해야 사용 가능. DPCD/EDID에서 DSC 능력 파싱
활용 예8K@60Hz (48 Gbps 필요 → HDMI 2.1 48 Gbps에 DSC 적용), DP MST에서 대역폭 절감
/* DSC 설정 예시 (드라이버 atomic_check에서) */
struct drm_dsc_config dsc_cfg = {};

dsc_cfg.line_buf_depth     = 13;
dsc_cfg.bits_per_component = 8;
dsc_cfg.bits_per_pixel     = 128;    /* 8 bpp × 16 (4비트 소수부) */
dsc_cfg.slice_width        = 1920;  /* 슬라이스 너비 */
dsc_cfg.slice_height       = 108;   /* 슬라이스 높이 */
dsc_cfg.pic_width          = 3840;
dsc_cfg.pic_height         = 2160;

/* RC (Rate Control) 파라미터 자동 계산 */
drm_dsc_compute_rc_parameters(&dsc_cfg);

/* PPS (Picture Parameter Set) 생성 → DP SDP 또는 HDMI infoframe으로 전송 */
drm_dsc_pps_payload_pack(pps_payload, &dsc_cfg);
Writeback Connector: DRM은 DRM_MODE_CONNECTOR_WRITEBACK 타입의 가상 커넥터를 지원합니다. 물리 디스플레이 대신 CRTC의 합성 결과를 GEM 버퍼에 기록합니다. 스크린 캡처, 녹화, 가상 디스플레이에 사용되며, drm_writeback_connector_init()으로 초기화합니다. Atomic commit에서 WRITEBACK_FB_IDWRITEBACK_OUT_FENCE_PTR property로 출력 버퍼와 완료 fence를 지정합니다.

fbdev 에뮬레이션

DRM/KMS는 /dev/fb0 (fbdev) 인터페이스를 에뮬레이션하여 레거시 애플리케이션과 콘솔 출력을 지원합니다. 커널 콘솔(fbcon), Plymouth(부팅 스플래시), 일부 임베디드 UI 프레임워크가 fbdev에 의존합니다.

DRM fbdev 에뮬레이션 구조 User Space (`/dev/fb0`) fbcon, Plymouth, 레거시 앱이 `mmap()`으로 프레임버퍼 접근 DRM fbdev helper 레이어 `drm_fbdev_shmem` / `drm_fbdev_ttm` / `drm_fbdev_dma` GEM/TTM 버퍼를 `fb_info`에 연결, `fb_dirty()`로 변경 알림 `fb_ops`를 DRM atomic 경로로 브리지 DRM/KMS Core Atomic Commit → Plane/CRTC/Connector 갱신 실제 디스플레이 하드웨어로 스캔아웃 드라이버 활성화 예 `#include <drm/drm_fbdev_shmem.h>` `drm_fbdev_shmem_setup(drm, 32)` 호출 시 `/dev/fb0` 자동 생성 `CONFIG_FRAMEBUFFER_CONSOLE=y`면 fbcon 자동 연결
fbdev 헬퍼메모리 관리사용 드라이버
drm_fbdev_shmem GEM shmem 기반 panfrost, lima, vc4, simpledrm, virtio-gpu
drm_fbdev_ttm TTM 기반 (VRAM) amdgpu, nouveau, radeon
drm_fbdev_dma DMA/CMA 기반 meson, sun4i, rockchip
드라이버 자체 구현 드라이버별 i915 (커스텀 fbdev)
fbcon과 DRM: CONFIG_FRAMEBUFFER_CONSOLE=y 설정 시, DRM 드라이버가 로드되면 자동으로 fbcon이 해당 fbdev에 연결됩니다. 이를 통해 커널 패닉 메시지, systemd 부팅 로그 등이 물리 디스플레이에 출력됩니다. fbcon=map:0 커널 파라미터로 fbcon이 사용할 fb 디바이스를 지정할 수 있습니다.

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>>> 구문으로 수천~수백만 스레드를 동시에 실행합니다.

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 명령어)
데이터 재배치분기 패턴이 같은 데이터를 워프 단위로 그룹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)디바이스 전역암묵적 직렬화단순 순차 실행
비기본 스트림스트림 단위스트림 내 순서 보장파이프라인, 다중 커널
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) 순으로 용량이 커지고 지연 시간이 증가합니다.

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 커널 모듈이 페이지 폴트 기반 마이그레이션을 처리하여, 프로그래머가 명시적 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: 양쪽 복제본 유지, 무효화 최소화
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× 높은 연산 처리량을 달성합니다.

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" 섹션에서 실측 오큐펀시와 병목 요인(레지스터/공유메모리/블록 수)을 확인하고, 성능 프로파일링 결과에 기반하여 조정하세요.

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 잠금: 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 컨테이너 / 가상화

클라우드 및 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 시간 분할 (컨텍스트 스위칭) 전체 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)를 컨테이너 네임스페이스에 바인드 마운트합니다. 컨테이너 이미지에는 CUDA 런타임만 포함하면 되며, 드라이버 버전은 호스트에서 자동으로 주입됩니다. 이를 통해 동일 이미지가 다양한 드라이버 버전의 호스트에서 동작합니다.

CUDA 컴파일 파이프라인

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

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실시간 모니터링 (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의 커널에서 직접 역참조할 수 있습니다(NVLink 시 ~900 GB/s, PCIe 시 ~32 GB/s).

CUDA 에러 처리 패턴

CUDA API 호출은 cudaError_t를 반환하며, 커널 실행 오류는 비동기적으로 발생합니다. 프로덕션 코드에서는 모든 CUDA 호출을 검사하는 매크로를 사용하고, 커널 실행 후 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 워커 프로세스를 분리하여, 오류 시 프로세스를 재시작하는 구조가 권장됩니다. 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로 메모리 단편화를 줄일 수 있습니다.
Linux 커널과 CUDA 통합 포인트 요약: CUDA 애플리케이션은 Linux 커널의 여러 서브시스템과 밀접하게 연동됩니다. PCIe: GPU 디바이스 열거/BAR 매핑(lspci -vvv), DMA: dma_map_sg()를 통한 scatter-gather DMA, IOMMU: VFIO를 통한 GPU 패스스루(가상화), cgroups: devices 컨트롤러로 GPU 접근 제어, 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

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 마이너크로스 벤더 해시 연산
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 소프트웨어 스택, 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 드라이버 → 하드웨어

참고 사항

커널 소스 참고 경로:
  • drivers/gpu/drm/ — DRM 코어 + 모든 GPU 드라이버
  • drivers/accel/ — compute accelerator 드라이버
  • include/drm/ — DRM 헤더 파일
  • include/uapi/drm/ — 유저 공간 API (ioctl, 구조체)
  • drivers/dma-buf/ — DMA-BUF 프레임워크
  • Documentation/gpu/ — 커널 공식 GPU 문서
  • Documentation/accel/ — 커널 공식 accelerator 문서
우선적으로 볼 1차 문서:
GPU 드라이버 개발 시 주의사항:
  • Fence 시그널링 규칙dma_fence는 반드시 유한 시간 내에 시그널되어야 합니다. 무한 대기 fence는 시스템 전체를 교착시킬 수 있습니다
  • 메모리 매핑 주의 — GPU VRAM을 WC(Write-Combining)로 매핑할 때 캐시 일관성에 주의. ioremap_wc() 사용
  • Atomic check 무결성atomic_check에서 부작용(side effect) 금지. 검증만 수행하고, 하드웨어 변경은 atomic_commit에서 수행
  • KMS 핫플러그 — Connector 상태 변경은 drm_kms_helper_hotplug_event()로 유저 공간에 통보. HPD(Hot Plug Detect) IRQ 핸들러에서 호출
  • GPU 리셋 격리 — 하나의 컨텍스트 행이 다른 컨텍스트에 영향을 주지 않도록 per-engine 또는 per-context 리셋 구현 권장
  • 펌웨어 로딩 — 최신 GPU는 대부분 request_firmware()로 마이크로코드를 로딩합니다. initramfs에 펌웨어 포함 필요 (CONFIG_EXTRA_FIRMWARE)

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