HMM (Heterogeneous Memory Management)

Linux 커널 HMM: CPU와 GPU가 동일한 가상 주소 공간(Address Space)을 공유하는 이기종 메모리 관리(Memory Management) 프레임워크. ZONE_DEVICE, migrate_vma, SVM, DMA-BUF 통합, AI/ML 워크로드 최적화 종합 가이드.

전제 조건: 고급 메모리 관리, NUMA, GPU 서브시스템 문서를 먼저 읽으세요. HMM은 CPU MMU와 GPU MMU를 동기화하는 복잡한 주제이므로, 페이지 테이블(Page Table)과 가상 메모리(Virtual Memory) 기초가 필수입니다.
일상 비유: HMM은 도서관 통합 예약 시스템과 비슷합니다. 여러 지점(CPU, GPU, NPU)이 같은 도서(메모리 페이지(Page))를 예약할 수 있고, 누군가가 책을 빌리면 다른 지점에서도 그 상태가 즉시 업데이트됩니다. 실제 책은 가장 자주 읽는 지점에 배치되며, 필요할 때 자동으로 이동합니다.

핵심 요약

  • 공유 가상 주소(Virtual Address) — CPU와 GPU가 동일한 포인터로 메모리 접근 (제로 카피)
  • ZONE_DEVICE — GPU VRAM을 Linux 페이지 프레임(Page Frame) 번호(PFN)로 등록하는 특수 메모리 존
  • migrate_vma — CPU 페이지를 GPU 메모리로 투명하게 이동시키는 API
  • HMM fault handler — GPU가 없는 페이지에 접근 시 CPU→GPU 마이그레이션 자동 처리
  • SVM (Shared Virtual Memory) — ROCm/CUDA Unified Memory의 커널 구현 기반
  • DMA-BUF + HMM — NPU/카메라 등 다른 가속기와 제로 카피 버퍼(Buffer) 공유
  • NUMA 티어링 — HMM 페이지를 NUMA 계층에 통합하여 자동 promotion/demotion
  • hmm_range_fault() — 드라이버가 CPU 페이지 테이블을 읽어 GPU 페이지 테이블 동기화

단계별 이해

  1. 가상 주소 공간 공유 이해
    CPU 프로세스(Process)의 VMA(가상 메모리 영역)를 GPU도 같이 사용하는 개념을 파악합니다.
  2. ZONE_DEVICE 등록 방법 학습
    GPU 드라이버가 VRAM을 Linux 메모리 서브시스템에 등록하는 과정을 이해합니다.
  3. migrate_vma API 실습
    페이지를 CPU↔GPU 간 이동시키는 API 사용법을 익힙니다.
  4. HMM fault handler 구현
    GPU가 없는 페이지에 접근했을 때 자동 마이그레이션이 일어나는 흐름을 추적합니다.
  5. SVM과 ROCm 연동 확인
    AMD ROCm의 KFD 드라이버가 HMM을 어떻게 활용하는지 소스에서 확인합니다.
  6. 진단 도구 활용
    bpftrace와 /proc/zoneinfo로 페이지 마이그레이션 동작을 관찰합니다.

HMM 개요

HMM(Heterogeneous Memory Management)은 CPU와 GPU가 동일한 가상 주소 공간을 공유하기 위한 Linux 커널 프레임워크입니다. 기존에는 GPU 메모리와 CPU 메모리가 완전히 분리되어 데이터를 사용하기 전에 명시적 복사(cudaMemcpy, clEnqueueCopyBuffer 등)가 필요했지만, HMM을 통해 CPU가 할당한 메모리에 GPU가 직접 접근하거나, GPU VRAM을 CPU가 직접 읽을 수 있습니다.

특성전통 GPU 메모리 모델HMM 모델
주소 공간CPU/GPU 별도공유 가상 주소
데이터 이동명시적 복사 필요자동 마이그레이션
포인터 공유불가 (GPU 전용 포인터)동일 포인터 사용
오버헤드(Overhead)복사 대역폭(Bandwidth) 비용페이지 폴트(Page Fault) 비용
APIcudaMemcpy, clEnqueue일반 malloc/mmap
구현드라이버 독자적커널 mm/hmm.c 공통
HMM의 등장 배경: AI/ML 워크로드에서 모델 파라미터와 입력 데이터가 수십 GB에 달하면서, CPU-GPU 간 명시적 복사 비용이 병목(Bottleneck)이 되었습니다. NVIDIA의 CUDA Unified Memory, AMD의 ROCm SVM, 그리고 HSA(Heterogeneous System Architecture) 표준이 모두 HMM 기반 커널 지원을 필요로 합니다.

HMM 발전 역사

HMM은 한 번에 완성된 것이 아니라 여러 커널 버전에 걸쳐 점진적으로 발전했습니다. 초기에는 GPU 드라이버마다 독자적인 메모리 관리 코드를 가졌지만, mm/hmm.c로 공통 프레임워크가 통합되면서 드라이버 코드가 대폭 간소화되었습니다.

커널 버전주요 변경영향
v4.14 (2017)HMM 초기 도입 (mm/hmm.c)hmm_mirror, hmm_devmem 기본 API 제공
v4.16 (2018)ZONE_DEVICE MEMORY_DEVICE_PRIVATE 도입GPU VRAM을 struct page로 관리 가능
v5.1 (2019)Nouveau(NVIDIA 오픈소스) HMM 지원첫 업스트림 GPU 드라이버의 HMM 활용
v5.2 (2019)amdkfd SVM 초기 HMM 통합AMD ROCm에서 HMM 기반 공유 가상 메모리
v5.10 (2020)hmm_range_fault() API 안정화hmm_mirror 폐기, mmu_interval_notifier로 전환
v5.14 (2021)MEMORY_DEVICE_COHERENT 추가CPU가 직접 load/store 가능한 장치 메모리 (CXL Type2)
v5.16 (2021)migrate_vma 리팩터링migrate_device.c 분리, 3단계 API 확립
v6.1 (2022)NUMA 메모리 티어링 프레임워크HMM 페이지를 NUMA 계층에 통합
v6.3 (2023)multi-GPU P2P 마이그레이션GPU-to-GPU 직접 페이지 이동(Page Migration) 지원
v6.6 (2023)HMM + Huge Pages 통합 개선2MB/1GB 단위 마이그레이션으로 TLB 효율 향상
v6.8+ (2024)CXL Type2 + HMM 연동 강화CXL 메모리 장치의 coherent HMM 접근
API 안정성 주의: HMM의 내부 API는 비교적 자주 변경되었습니다. v5.10 이전의 hmm_mirror 구조체(Struct)는 완전히 폐기되었고, 현재는 mmu_interval_notifier + hmm_range_fault() 조합이 표준입니다. 드라이버 코드에서 hmm_mirror_register()를 호출하는 코드를 발견하면 v5.10 이전의 구식 코드이므로 참고만 하세요.

HMM 아키텍처

HMM의 핵심은 CPU 페이지 테이블과 GPU 페이지 테이블을 동기화하는 메커니즘입니다. mmu_notifier를 통해 CPU 페이지 테이블 변경(매핑(Mapping) 해제, 페이지 이동 등)을 GPU 드라이버에 통지합니다.

User Space (CPU 프로세스) malloc / mmap (VMA) ROCm / CUDA Unified Memory OpenCL SVM DMA-BUF export CPU 커널 (mm/hmm.c) CPU 페이지 테이블 mmu_notifier hmm_range_fault() · migrate_vma_setup() GPU 드라이버 (amdkfd / i915) GPU 페이지 테이블 IOMMU 매핑 HMM fault handler · GPU MMU invalidate notifier ZONE_DEVICE (device_private pages) CPU 페이지 프레임 번호로 VRAM 페이지 등록 GPU VRAM / HBM 실제 GPU 물리 메모리 (HBM2/HBM3) migrate CPU 시스템 메모리 (DDR5) GPU HBM3 / GDDR7 PCIe 6.0 / CXL / NVLink

핵심 구조체

#include <linux/hmm.h>
#include <linux/migrate.h>
#include <linux/mmu_notifier.h>

/* HMM 범위 — 특정 VMA 범위의 페이지 상태 조회 */
struct hmm_range {
    struct mmu_notifier_range  notifier;       /* 시작/끝 주소 */
    struct mmu_interval_notifier *notifier_seq; /* 시퀀스 번호 */
    unsigned long             *hmm_pfns;       /* 결과 PFN 배열 */
    unsigned long              default_flags;  /* HMM_PFN_REQ_* */
    unsigned long              pfn_flags_mask;
    unsigned long              dev_private_owner; /* 드라이버 식별자 */
};

/* HMM PFN 플래그 */
// HMM_PFN_VALID     : 유효한 페이지 (물리 주소 존재)
// HMM_PFN_WRITE     : 쓰기 가능 (COW 고려)
// HMM_PFN_ERROR     : 접근 오류
// HMM_PFN_NONE      : 미매핑 (demand fault 필요)

mmu_notifier — 페이지 테이블 변경 알림

/* GPU 드라이버에서 mmu_notifier 등록 */
static const struct mmu_notifier_ops my_gpu_mmu_ops = {
    .invalidate_range_start = my_gpu_invalidate_start,
    .invalidate_range_end   = my_gpu_invalidate_end,
};

static int my_gpu_invalidate_start(
    struct mmu_notifier *mn,
    const struct mmu_notifier_range *range)
{
    struct my_gpu_ctx *ctx = container_of(mn, struct my_gpu_ctx, notifier);

    /* CPU 페이지 테이블 변경 전 GPU MMU를 먼저 무효화 */
    my_gpu_unmap_range(ctx, range->start, range->end);
    return 0;
}

/* GPU 컨텍스트에 notifier 등록 */
mmu_notifier_register(&ctx->notifier, current->mm);

HMM 페이지 폴트 처리 흐름

HMM의 핵심 동작은 페이지 폴트 기반 마이그레이션입니다. CPU 또는 GPU가 현재 상대방 메모리에 있는 페이지에 접근하면 폴트가 발생하고, HMM 프레임워크가 자동으로 페이지를 적절한 위치로 이동시킵니다.

GPU 페이지 폴트 흐름 (CPU→GPU 마이그레이션)

GPU 페이지 폴트 → CPU→GPU 마이그레이션 전체 흐름 ① GPU가 가상 주소 VA에 접근 시도 ② GPU MMU: PTE 없음 → 폴트 인터럽트 ③ GPU 드라이버 폴트 핸들러: hmm_range_fault() 호출 ④ CPU 페이지 테이블 조회 mmap_read_lock → walk_page_range → PFN + 권한 획득 페이지 위치? 이미 GPU에 있음 → GPU PTE만 업데이트 CPU DDR에 있음 → 마이그레이션 필요 스왑 아웃 / 미할당 → 스왑 인 + 마이그레이션 ⑤ migrate_vma_setup → DMA 복사 → migrate_vma_finalize CPU PTE: VA → device_private PFN | GPU PTE: VA → VRAM 물리 주소 GPU 실행 재개 (폴트 해결) PCIe DMA 또는 NVLink P2P 전송

CPU 페이지 폴트 흐름 (GPU→CPU 마이그레이션)

CPU가 현재 GPU VRAM에 있는 device_private 페이지에 접근하면, CPU MMU가 폴트를 발생시키고 dev_pagemap_ops.migrate_to_ram 콜백(Callback)이 호출됩니다. 이 콜백은 GPU에서 CPU로 데이터를 DMA 복사한 뒤, 일반 CPU 페이지로 교체합니다.

/* CPU가 device_private 페이지에 접근 → 자동 GPU→CPU 마이그레이션 */
/* mm/memory.c의 handle_pte_fault()에서 호출되는 경로: */

/*
 * handle_pte_fault()
 *   → do_swap_page()
 *     → is_device_private_entry(entry)  → true
 *       → pgmap->ops->migrate_to_ram(page)
 */

static vm_fault_t my_gpu_migrate_to_ram(struct vm_fault *vmf)
{
    struct page *dev_page = vmf->page;
    struct my_gpu_dev *gpu = dev_page_to_gpu(dev_page);
    struct page *dpage, *spage;
    struct migrate_vma args = { 0 };
    unsigned long src_pfn, dst_pfn;

    /* CPU 측 타겟 페이지 할당 */
    dpage = alloc_page_vma(GFP_HIGHUSER, vmf->vma, vmf->address);
    if (!dpage)
        return VM_FAULT_OOM;

    /* migrate_vma 설정: device_private → 일반 페이지 */
    args.vma        = vmf->vma;
    args.src        = &src_pfn;
    args.dst        = &dst_pfn;
    args.start      = vmf->address;
    args.end        = vmf->address + PAGE_SIZE;
    args.pgmap_owner = gpu;
    args.flags      = MIGRATE_VMA_SELECT_DEVICE_PRIVATE;

    src_pfn = migrate_pfn(page_to_pfn(dev_page)) | MIGRATE_PFN_MIGRATE;
    dst_pfn = migrate_pfn(page_to_pfn(dpage));

    if (migrate_vma_setup(&args))
        goto err;

    /* GPU VRAM → CPU DDR: DMA 복사 (동기) */
    gpu_dma_copy_to_ram(gpu,
                        page_to_phys(dpage),      /* dst: DDR 물리 주소 */
                        gpu_vram_addr(dev_page),  /* src: VRAM 주소 */
                        PAGE_SIZE);

    migrate_vma_pages(&args);
    migrate_vma_finalize(&args);
    return 0;

err:
    __free_page(dpage);
    return VM_FAULT_SIGBUS;
}
성능 주의: CPU→GPU 또는 GPU→CPU 마이그레이션은 PCIe를 통한 DMA 복사를 수반하므로 지연(Latency) 시간이 수 마이크로초에 달합니다. 핫 페이지가 빈번하게 양방향으로 이동하면 성능이 급격히 저하됩니다. 이 현상을 thrashing이라고 하며, AMD ROCm의 preferred_location 속성이나 NVIDIA의 cudaMemAdvise()를 사용해 페이지 고정 위치를 지정하는 것이 권장됩니다.

HMM PFN 플래그 상세

hmm_range_fault()가 반환하는 PFN 배열의 각 항목에는 페이지 상태를 나타내는 플래그가 포함됩니다. GPU 드라이버는 이 플래그를 해석하여 GPU 페이지 테이블에 적절한 매핑을 생성합니다.

플래그비트의미드라이버 동작
HMM_PFN_VALIDbit 0유효한 PFN — 물리 주소(Physical Address) 존재GPU PTE에 물리 주소 매핑
HMM_PFN_WRITEbit 1쓰기 가능 — COW 완료GPU PTE에 W(쓰기) 비트 설정
HMM_PFN_ERRORbit 2접근 오류 — 읽기 불가해당 VA 접근 금지 설정
HMM_PFN_ORDER_SHIFTbit 56-63복합 페이지 크기 (huge page)GPU 대형 페이지 매핑 가능
HMM_PFN_REQ_FAULT입력 플래그페이지 폴트 강제 (demand paging)default_flags에 설정
HMM_PFN_REQ_WRITE입력 플래그쓰기 접근 요청 (COW 트리거)default_flags에 설정
/* PFN 플래그 해석 예시 — GPU 페이지 테이블 빌더 */
static void gpu_build_ptes(struct my_gpu_ctx *ctx,
                            unsigned long *pfns,
                            unsigned long npages,
                            unsigned long va_start)
{
    unsigned long i;

    for (i = 0; i < npages; i++) {
        unsigned long pfn = pfns[i];
        unsigned long va  = va_start + (i << PAGE_SHIFT);

        if (!(pfn & HMM_PFN_VALID)) {
            /* 미매핑 — GPU PTE도 무효화 */
            gpu_pte_clear(ctx, va);
            continue;
        }

        if (pfn & HMM_PFN_ERROR) {
            /* 오류 페이지 — 접근 시 GPU 예외 발생하도록 */
            gpu_pte_set_poison(ctx, va);
            continue;
        }

        uint64_t phys = hmm_pfn_to_phys(pfn);
        uint64_t flags = GPU_PTE_READABLE;

        if (pfn & HMM_PFN_WRITE)
            flags |= GPU_PTE_WRITABLE;

        /* huge page 감지 (order > 0이면 대형 페이지 매핑) */
        unsigned int order = hmm_pfn_to_map_order(pfn);
        if (order >= 9)  /* 2MB 이상 */
            flags |= GPU_PTE_HUGE_2M;

        gpu_pte_set(ctx, va, phys, flags);
    }
}

ZONE_DEVICE와 device_private 페이지

ZONE_DEVICE는 GPU VRAM, PMEM(영구 메모리) 등 CPU 직접 접근이 제한된 장치 메모리를 Linux 페이지 프레임 번호(PFN) 시스템에 통합하는 특수 메모리 존입니다. 이를 통해 커널 메모리 관리 코드가 GPU 메모리 페이지를 일반 페이지처럼 다룰 수 있습니다.

ZONE_DEVICE 등록 절차

#include <linux/memremap.h>

/* GPU 드라이버 초기화 시 VRAM을 ZONE_DEVICE로 등록 */
static int my_gpu_register_vram(struct my_gpu_dev *gpu)
{
    struct dev_pagemap *pgmap = &gpu->pgmap;

    pgmap->type          = MEMORY_DEVICE_PRIVATE;  /* GPU 전용 메모리 */
    pgmap->range.start   = gpu->vram_phys_base;
    pgmap->range.end     = gpu->vram_phys_base + gpu->vram_size - 1;
    pgmap->nr_range      = 1;
    pgmap->ops           = &my_gpu_pgmap_ops;      /* migrate_to_ram 등 */

    /* VRAM 물리 주소를 struct page 배열에 연결 */
    gpu->vram_pages = memremap_pages(pgmap, dev_to_node(gpu->dev));
    if (IS_ERR(gpu->vram_pages))
        return PTR_ERR(gpu->vram_pages);

    return 0;
}

/* dev_pagemap 오퍼레이션 — 페이지를 CPU로 복구 */
static const struct dev_pagemap_ops my_gpu_pgmap_ops = {
    /* GPU → CPU 마이그레이션 (swapout 등에서 호출) */
    .migrate_to_ram = my_gpu_migrate_to_ram,
    /* device_private 페이지 reference count 0 시 호출 */
    .page_free      = my_gpu_page_free,
};
ZONE_DEVICE 유형용도접근 방식
MEMORY_DEVICE_PRIVATEGPU VRAM (CPU 직접 접근 불가)migrate_vma로만 접근
MEMORY_DEVICE_COHERENTCXL Type2, GPU UMAP 영역CPU load/store 가능
MEMORY_DEVICE_FS_DAXDAX 파일시스템 (PMEM)DAX mmap
MEMORY_DEVICE_GENERIC일반 장치 메모리DMA 전용

Coherent Device Memory (MEMORY_DEVICE_COHERENT)

MEMORY_DEVICE_COHERENT는 v5.14에서 도입된 ZONE_DEVICE 유형으로, CPU가 직접 load/store로 접근 가능한 장치 메모리를 지원합니다. MEMORY_DEVICE_PRIVATE과 달리 CPU 접근 시 자동 마이그레이션이 필요 없으며, CXL Type2 장치나 GPU의 CPU-접근 가능 VRAM 영역(일명 "doorbell" 또는 "system" 영역)에 사용됩니다.

특성MEMORY_DEVICE_PRIVATEMEMORY_DEVICE_COHERENT
CPU 직접 접근불가 (fault → migrate_to_ram)가능 (load/store)
대표 장치GPU VRAM (discrete)CXL Type2, APU 통합 메모리
주소 공간CPU에서 비가시CPU 물리 주소 공간에 매핑
마이그레이션migrate_vma 필수선택적 (성능 최적화용)
캐시 일관성(Cache Coherency)없음 (DMA만)HW 코히런시 (CXL.cache)
NUMA 노드별도 노드 (접근 불가)별도 노드 (접근 가능, 높은 지연)
/* MEMORY_DEVICE_COHERENT 등록 (CXL Type2 가속기) */
static int cxl_accel_register_coherent(struct cxl_accel *accel)
{
    struct dev_pagemap *pgmap = &accel->pgmap;

    pgmap->type        = MEMORY_DEVICE_COHERENT;
    pgmap->range.start = accel->mem_phys_base;
    pgmap->range.end   = accel->mem_phys_base + accel->mem_size - 1;
    pgmap->nr_range    = 1;
    pgmap->ops         = &cxl_coherent_pgmap_ops;

    /* CPU가 직접 접근 가능 — NUMA 노드로 등록 */
    accel->mem_pages = memremap_pages(pgmap,
                                      dev_to_node(accel->dev));
    if (IS_ERR(accel->mem_pages))
        return PTR_ERR(accel->mem_pages);

    /* memory_tier에 등록 (demotion 대상) */
    set_node_memory_tier(dev_to_node(accel->dev), 1);  /* tier 1 */

    pr_info("CXL accel: %zu MB coherent memory on NUMA node %d\n",
            accel->mem_size >> 20, dev_to_node(accel->dev));
    return 0;
}

/* Coherent 페이지는 migrate_to_ram 대신 직접 접근 */
static const struct dev_pagemap_ops cxl_coherent_pgmap_ops = {
    /* migrate_to_ram은 NULL — CPU가 직접 접근 가능 */
    .page_free = cxl_coherent_page_free,
};
CXL Type2와 HMM 시너지: CXL Type2 장치(GPU/FPGA 등)는 CXL.cache 프로토콜로 CPU 캐시(Cache)와 일관성을 유지하면서 자체 메모리를 노출합니다. 이 메모리를 MEMORY_DEVICE_COHERENT로 등록하면 CPU가 직접 접근하되, 자주 접근하는 페이지만 migrate_vma로 DDR로 승격(promotion)하여 지연 시간을 줄일 수 있습니다. 자세한 내용은 CXL 메모리 문서를 참고하세요.

SVM과 AMD HSA/ROCm 통합

SVM(Shared Virtual Memory)은 CPU와 GPU가 동일한 가상 주소를 사용하는 프로그래밍 모델입니다. AMD의 HSA(Heterogeneous System Architecture)와 ROCm의 KFD(Kernel Fusion Driver)가 Linux HMM을 기반으로 SVM을 구현합니다.

ROCm KFD SVM 구현

/* drivers/gpu/drm/amd/amdkfd/kfd_svm.c 핵심 흐름 */

/* SVM 영역 등록 — ROCm runtime이 호출 */
int svm_range_add(struct kfd_process *p,
                  uint64_t start, uint64_t size,
                  uint32_t nattr,
                  struct kfd_ioctl_svm_attribute *attrs)
{
    struct svm_range *prange;

    /* HMM mmu_interval_notifier 등록 */
    mmu_interval_notifier_insert(&prange->notifier,
                                  mm, start, size,
                                  &svm_range_mn_ops);

    /* NUMA 선호도, 마이그레이션 정책 설정 */
    svm_range_set_attr(p, mm, start, size, nattr, attrs);
    return 0;
}

/* GPU 페이지 폴트 핸들러 */
static int svm_range_restore_pages(struct amdgpu_device *adev,
                                    unsigned int pasid,
                                    uint64_t addr)
{
    /* 1. CPU 페이지 테이블 조회 (hmm_range_fault) */
    hmm_range_fault(&range);

    /* 2. CPU 메모리 → GPU VRAM 마이그레이션 */
    svm_migrate_to_vram(prange, addr, adev, mm);

    /* 3. GPU 페이지 테이블 업데이트 */
    svm_range_map_to_gpu(adev, mm, prange, addr, 1, NULL);
    return 0;
}
CUDA Unified Memory와의 비교: NVIDIA는 자체 UVM(Unified Virtual Memory) 드라이버로 비슷한 기능을 구현합니다. CUDA 8.0+ Unified Memory는 cudaMallocManaged()로 할당하고, 페이지 폴트 시 자동 마이그레이션이 이루어집니다. AMD ROCm은 동일한 패턴을 Linux HMM 표준 API 위에 구현하여 커널 업스트림과 더 긴밀하게 통합됩니다.

DMA-BUF와 HMM 결합

DMA-BUF는 여러 디바이스 드라이버가 동일한 메모리 버퍼를 공유하는 메커니즘입니다. HMM과 결합하면 GPU 메모리, NPU 메모리, 카메라 버퍼가 모두 같은 물리 메모리(Physical Memory)를 참조할 수 있어 제로 카피 AI 파이프라인(Pipeline)을 구현할 수 있습니다.

HMM + DMA-BUF 통합 패턴

/* GPU 메모리를 DMA-BUF로 export (NPU/카메라와 공유) */
static struct dma_buf *my_gpu_gem_export(
    struct drm_gem_object *obj,
    int flags)
{
    DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
    exp_info.ops  = &my_gpu_dma_buf_ops;
    exp_info.size = obj->size;
    exp_info.flags = flags;
    exp_info.priv  = obj;
    return dma_buf_export(&exp_info);
}

/* NPU 드라이버에서 GPU 버퍼를 import하여 HMM 범위 조회 */
int npu_import_gpu_buffer(struct dma_buf *dmabuf)
{
    struct dma_buf_attachment *attach;
    struct sg_table           *sgt;

    attach = dma_buf_attach(dmabuf, npu->dev);
    sgt    = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);

    /* sg_table의 물리 주소를 NPU IOMMU에 매핑 */
    npu_iommu_map_sg(npu, sgt);
    return 0;
}
시나리오관련 API메모리 이동
GPU → NPU 공유DMA-BUF export/import없음 (동일 VRAM)
GPU VRAM → CPU 접근migrate_vma (VRAM→DDR)PCIe 복사
카메라 → GPU 직접DMA-BUF + IOMMU없음 (DMA)
CPU malloc → GPU 연산HMM fault + migratePCIe 마이그레이션

migrate_vma 마이그레이션 흐름

페이지를 CPU 메모리에서 GPU VRAM으로 이동시키는 전체 흐름을 설명합니다. migrate_vma_setup()migrate_vma_pages()migrate_vma_finalize() 3단계로 구성됩니다.

GPU 페이지 폴트 발생 ① migrate_vma_setup() CPU 페이지 테이블 락, 소스 페이지 핀 ② GPU에서 목표 페이지 할당 VRAM에서 빈 페이지 확보 (device_private) ③ migrate_vma_pages() DMA로 DDR → VRAM 데이터 복사 ④ migrate_vma_finalize() CPU 페이지 테이블: DDR 매핑 → device_private PFN mm_lock() ptrace_stop() alloc_pages_node() VRAM 예산 확인 dma_async_memcpy() PCIe P2P DMA CPU PTE 업데이트 GPU PTE 업데이트
/* GPU 드라이버에서 migrate_vma 사용 예시 */
static int gpu_migrate_to_vram(struct my_gpu *gpu,
                               struct vm_area_struct *vma,
                               unsigned long start,
                               unsigned long end)
{
    struct migrate_vma args = {
        .vma        = vma,
        .dst        = dst_pages,     /* GPU VRAM 목표 PFN 배열 */
        .src        = src_pages,     /* CPU 소스 PFN 배열 */
        .start      = start,
        .end        = end,
        .pgmap_owner = gpu,          /* device_private 소유자 */
        .flags      = MIGRATE_VMA_SELECT_SYSTEM, /* CPU 페이지만 선택 */
    };

    /* 단계 1: 소스 페이지 조회 및 락 */
    int ret = migrate_vma_setup(&args);
    if (ret) return ret;

    /* 단계 2: GPU VRAM 페이지 할당 및 DMA 복사 */
    gpu_alloc_and_copy(gpu, &args);

    /* 단계 3: CPU 페이지 테이블 업데이트 완료 */
    migrate_vma_pages(&args);
    migrate_vma_finalize(&args);
    return 0;
}

NUMA 티어링과 HMM

Linux 6.1+의 NUMA 메모리 티어링(Memory Tiering) 프레임워크는 HMM과 협력하여 빠른 메모리(DDR5)에서 느린 메모리(CXL, GPU VRAM)로 덜 쓰이는 페이지를 자동 demotion하고, 자주 접근하는 페이지는 faster tier로 promotion합니다.

Tier 0 (가장 빠름) CPU 로컬 DDR5 · 지연: ~80ns · 대역폭: ~200 GB/s ZONE_NORMAL NUMA node 0 Tier 1 (중간) CXL Type3 메모리 · 지연: ~200-400ns · 대역폭: ~50 GB/s ZONE_DEVICE / ZONE_NORMAL NUMA node 2 (CXL) Tier 2 (GPU VRAM) HBM3 VRAM · 지연: ~1µs (PCIe) / 읽기 불가 (device_private) ZONE_DEVICE (PRIVATE) NUMA node 3 (GPU) demotion promotion 자동 티어링 도구 autonuma (NUMA balancing) · memory_tier sysfs · numactl --preferred · move_pages()
# NUMA 메모리 티어링 설정
# 자동 NUMA 밸런싱 활성화 (HMM과 함께 동작)
echo 1 > /proc/sys/kernel/numa_balancing

# 메모리 티어 구조 확인
cat /sys/devices/virtual/memory_tiering/memory_tier0/nodelist
cat /sys/devices/virtual/memory_tiering/memory_tier1/nodelist

# 프로세스를 Tier1(CXL)에서 실행
numactl --membind=2 ./ai_inference_job

# 특정 프로세스의 메모리를 GPU 노드로 이동
# (migrate_pages 시스콜 사용)
move_pages $(pidof my_app) 1 NULL 3 NULL MPOL_MF_MOVE_ALL

Multi-GPU P2P 마이그레이션

AI/ML 워크로드에서는 여러 GPU가 동시에 같은 데이터에 접근하거나, 모델 파라미터를 GPU 간에 분산해야 하는 경우가 흔합니다. HMM은 GPU-to-GPU 직접 페이지 마이그레이션(P2P migration)을 지원하여 CPU 메모리를 경유하지 않고 GPU 간 직접 데이터를 이동시킬 수 있습니다.

Multi-GPU P2P 마이그레이션 경로 CPU mm_struct 페이지 테이블 (device_private PTE) GPU 0 (VRAM) ZONE_DEVICE node 2 페이지 A, B (현재 위치) GPU 1 (VRAM) ZONE_DEVICE node 3 페이지 A', B' (마이그레이션 대상) mmu_notifier mmu_notifier PCIe P2P / NVLink 인터커넥트 기술 PCIe 5.0 P2P: ~50 GB/s | NVLink 4.0: ~900 GB/s | xGMI (AMD): ~100 GB/s | CXL.mem: ~64 GB/s
/* GPU-to-GPU P2P 마이그레이션 (GPU0 → GPU1) */
static int gpu_p2p_migrate(struct my_gpu *src_gpu,
                           struct my_gpu *dst_gpu,
                           struct vm_area_struct *vma,
                           unsigned long start,
                           unsigned long end)
{
    struct migrate_vma args = {
        .vma         = vma,
        .src         = src_pfns,
        .dst         = dst_pfns,
        .start       = start,
        .end         = end,
        .pgmap_owner = src_gpu,  /* 소스 GPU의 device_private만 선택 */
        .flags       = MIGRATE_VMA_SELECT_DEVICE_PRIVATE,
    };

    int ret = migrate_vma_setup(&args);
    if (ret) return ret;

    /* 목적지 GPU에서 페이지 할당 */
    gpu_alloc_device_pages(dst_gpu, dst_pfns, npages);

    /* P2P DMA 복사 (PCIe BAR-to-BAR 또는 NVLink) */
    gpu_p2p_dma_copy(src_gpu, dst_gpu,
                     src_pfns, dst_pfns, npages);

    migrate_vma_pages(&args);
    migrate_vma_finalize(&args);

    /* CPU PTE: src_gpu device_private → dst_gpu device_private */
    /* GPU1 PTE: VA → VRAM1 물리 주소 */
    return 0;
}
P2P DMA 요구사항: GPU-to-GPU P2P DMA가 작동하려면 두 GPU가 같은 PCIe 루트 포트 아래에 있거나, IOMMU가 P2P를 허용하도록 구성되어야 합니다. CONFIG_PCI_P2PDMA=y 커널 옵션이 필요하며, pci_p2pdma_distance()로 P2P 가능 여부와 예상 대역폭을 확인할 수 있습니다. NVIDIA NVLink나 AMD xGMI 같은 전용 인터커넥트는 PCIe보다 훨씬 높은 대역폭을 제공합니다.

성능 최적화와 Thrashing 방지

HMM 기반 공유 가상 메모리의 최대 장점은 프로그래밍 편의성이지만, 페이지 마이그레이션 비용을 고려하지 않으면 오히려 명시적 복사보다 성능이 나빠질 수 있습니다. 이 섹션에서는 HMM 성능을 최적화하는 실전 기법을 다룹니다.

Thrashing 문제와 해결

CPU와 GPU가 교대로 같은 페이지에 접근하면, 페이지가 CPU↔GPU 사이를 반복적으로 이동합니다. 이를 thrashing이라 하며, 매 이동마다 PCIe DMA 복사 비용(수~수십 마이크로초)이 발생합니다.

해결 기법설명API / 설정
선호 위치 지정페이지의 기본 위치를 CPU 또는 특정 GPU로 고정ROCm: KFD_IOCTL_SVM_ATTR_PREFERRED_LOC
접근 카운터GPU HW 접근 카운터로 실제 핫 페이지만 마이그레이션NVIDIA: access counter, AMD: XNACK retry
프리페치실행 전에 필요한 페이지를 미리 마이그레이션ROCm: svm_migrate_to_vram() 명시적 호출
읽기 전용(Read-Only) 복제읽기만 하는 페이지는 CPU/GPU 양쪽에 복사본 유지HMM_PFN_VALID (쓰기 플래그 없음)
대형 페이지2MB 단위 마이그레이션으로 TLB 미스/폴트 수 감소CONFIG_TRANSPARENT_HUGEPAGE + HMM
마이그레이션 임계값일정 폴트 횟수 이상일 때만 마이그레이션 수행드라이버 정책 (fault counter)
/* ROCm SVM: 선호 위치 및 접근 정책 설정 (유저스페이스) */
#include <hsakmt.h>

/* 특정 메모리 범위를 GPU 0에 고정 */
HSAuint32 gpu_id = 0;
HsaMemFlags flags;
struct kfd_ioctl_svm_attribute attrs[] = {
    { .type  = KFD_IOCTL_SVM_ATTR_PREFERRED_LOC,
      .value = gpu_id },
    { .type  = KFD_IOCTL_SVM_ATTR_SET_FLAGS,
      .value = KFD_IOCTL_SVM_FLAG_GPU_RO },  /* GPU 읽기 전용 */
};

/* 커널 SVM에 속성 전달 */
hsaKmtSVMSetAttr(ptr, size,
                  2,           /* nattr */
                  attrs);

/* 명시적 프리페치 (AI 배치 시작 전) */
hsaKmtSVMSetAttr(model_weights, weight_size,
                  1,
                  &(struct kfd_ioctl_svm_attribute){
                      .type  = KFD_IOCTL_SVM_ATTR_PREFETCH_LOC,
                      .value = gpu_id
                  });

성능 측정 메트릭

# HMM 마이그레이션 횟수 및 바이트 추적
bpftrace -e '
kretprobe:migrate_vma_finalize {
    @migrations = count();
}

kprobe:migrate_vma_setup {
    /* start와 end의 차이로 마이그레이션 크기 추적 */
    @bytes = hist(((struct migrate_vma *)arg0)->end -
                  ((struct migrate_vma *)arg0)->start);
}'

# PCIe DMA 대역폭 사용량 확인 (perf)
perf stat -e 'uncore_iio/event=0x83,umask=0x04/' \
          -e 'uncore_iio/event=0xc0,umask=0x04/' \
          -p $(pidof my_app) -- sleep 10

# AMD GPU의 XNACK(재시도) 폴트 통계
cat /sys/class/drm/card0/device/ras/gpu_fault_count
cat /sys/kernel/debug/dri/0/amdgpu_vm_info
벤치마크 주의: HMM 오버헤드를 측정할 때는 첫 실행과 이후 실행을 구분해야 합니다. 첫 실행에는 cold 폴트(모든 페이지가 마이그레이션)가 발생하여 매우 느리고, 이후 실행에서는 페이지가 이미 GPU에 있어 폴트가 적습니다. AI 추론 레이턴시 측정 시 반드시 warm-up 실행을 포함하세요.

드라이버 API — hmm_range_fault()

hmm_range_fault()는 GPU 드라이버가 CPU 페이지 테이블의 현재 상태를 조회하여 GPU 페이지 테이블을 동기화할 때 사용하는 핵심 API입니다. CPU 페이지가 스왑(Swap) 아웃되어 있으면 자동으로 스왑 인도 수행합니다.

/* hmm_range_fault() 사용 예시 — CPU → GPU PTE 동기화 */
static int gpu_sync_page_table(struct my_gpu_ctx *ctx,
                               unsigned long start,
                               unsigned long end)
{
    unsigned long npages = (end - start) >> PAGE_SHIFT;
    unsigned long *pfns;
    struct hmm_range range;
    int ret;

    pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL);

    range.notifier       = &ctx->interval_notifier;
    range.start          = start;
    range.end            = end;
    range.hmm_pfns       = pfns;
    range.default_flags  = HMM_PFN_REQ_FAULT | HMM_PFN_REQ_WRITE;

retry:
    range.notifier_seq = mmu_interval_read_begin(range.notifier);

    mmap_read_lock(ctx->mm);
    ret = hmm_range_fault(&range);  /* CPU 페이지 테이블 조회 + fault */
    mmap_read_unlock(ctx->mm);

    if (ret == -EBUSY) goto retry;    /* 동시 수정 발생 시 재시도 */
    if (ret) goto err;

    /* 시퀀스 번호로 동시성 확인 */
    if (mmu_interval_read_retry(range.notifier, range.notifier_seq))
        goto retry;

    /* pfns 배열로 GPU 페이지 테이블 업데이트 */
    gpu_update_page_table(ctx, pfns, npages, start);

err:
    kvfree(pfns);
    return ret;
}

mmu_interval_notifier

/* mmu_interval_notifier: 특정 VMA 구간 변경 감지 */
static const struct mmu_interval_notifier_ops gpu_mn_ops = {
    .invalidate = gpu_mn_invalidate,
};

static bool gpu_mn_invalidate(
    struct mmu_interval_notifier *mni,
    const struct mmu_notifier_range *range,
    unsigned long cur_seq)
{
    /* 시퀀스 번호 기록 (hmm_range_fault retry 감지용) */
    mmu_interval_set_seq(mni, cur_seq);

    /* GPU 해당 VA 범위 flush (비동기 가능) */
    gpu_tlb_flush_range(mni->start, mni->end);
    return true;
}

/* GPU 컨텍스트 생성 시 등록 */
mmu_interval_notifier_insert(&ctx->interval_notifier, mm,
                              vma_start, vma_size, &gpu_mn_ops);

진단 및 디버깅(Debugging)

procfs / sysfs 진단

# ZONE_DEVICE 통계 확인
cat /proc/zoneinfo | grep -A20 "zone Device"

# 특정 프로세스의 HMM 관련 페이지 맵 확인
cat /proc/$(pidof my_app)/smaps | grep -A15 "Huge"

# NUMA 페이지 마이그레이션 통계
cat /proc/vmstat | grep numa_
# numa_page_migrated : 마이그레이션된 페이지 수
# numa_hint_faults   : NUMA 힌트 폴트 수

# GPU 드라이버 HMM 통계 (amdgpu 예시)
cat /sys/kernel/debug/dri/0/amdgpu_vm_info

bpftrace로 마이그레이션 추적

# migrate_vma_setup 호출 추적
bpftrace -e '
kprobe:migrate_vma_setup {
    printf("[HMM] migrate_vma_setup: start=%lx end=%lx pid=%d\n",
           ((struct migrate_vma *)arg0)->start,
           ((struct migrate_vma *)arg0)->end,
           pid);
}'

# hmm_range_fault 지연 시간 측정
bpftrace -e '
kprobe:hmm_range_fault { @start[tid] = nsecs; }
kretprobe:hmm_range_fault /@start[tid]/ {
    @latency = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}'

# ZONE_DEVICE 페이지 폴트 이벤트
bpftrace -e '
tracepoint:migrate:mm_migrate_pages {
    printf("migrated: mode=%d pages=%lu\n", args->mode, args->nr_succeeded);
}'

ftrace HMM 이벤트

# ftrace로 HMM 관련 이벤트 활성화
cd /sys/kernel/debug/tracing
echo 1 > events/migrate/enable
echo 1 > events/mmu_notifier/enable
echo 1 > tracing_on
cat trace_pipe | grep hmm

HMM 테스트 모듈 (lib/test_hmm.c)

Linux 커널은 HMM API를 검증하기 위한 자체 테스트 모듈 lib/test_hmm.c를 포함합니다. 이 모듈은 가상의 디바이스를 생성하여 ZONE_DEVICE, migrate_vma, hmm_range_fault() 등 모든 HMM API를 실제 GPU 없이 테스트할 수 있게 합니다. HMM 드라이버를 개발할 때 참고 코드로 매우 유용합니다.

/* lib/test_hmm.c — HMM 테스트 모듈의 핵심 구조 */

/* 가상 디바이스 구조체 */
struct dmirror_device {
    struct cdev        cdevice;         /* 문자 디바이스 */
    struct dev_pagemap pagemap;         /* ZONE_DEVICE 관리 */
    unsigned long     *pfns;           /* HMM PFN 배열 */
    void              *mdevice_pages;  /* 가상 VRAM */
    struct mutex       devmem_lock;
};

/* 테스트용 ioctl 명령 */
#define HMM_DMIRROR_READ        _IOWR('H', 0x00, struct hmm_dmirror_cmd)
#define HMM_DMIRROR_WRITE       _IOWR('H', 0x01, struct hmm_dmirror_cmd)
#define HMM_DMIRROR_MIGRATE_TO_DEV _IOWR('H', 0x02, struct hmm_dmirror_cmd)
#define HMM_DMIRROR_MIGRATE_TO_SYS _IOWR('H', 0x03, struct hmm_dmirror_cmd)
#define HMM_DMIRROR_SNAPSHOT    _IOWR('H', 0x04, struct hmm_dmirror_cmd)

/* migrate_to_ram 콜백 — 테스트 디바이스에서 CPU로 복귀 */
static vm_fault_t dmirror_devmem_fault(struct vm_fault *vmf)
{
    struct dmirror_device *mdevice;
    unsigned long src, dst;

    mdevice = dmirror_page_to_device(vmf->page);

    /* 가상 VRAM → CPU 메모리 복사 */
    memcpy(page_address(dpage),
           dmirror_devmem_addr(vmf->page),
           PAGE_SIZE);
    /* ... migrate_vma 호출 ... */
    return 0;
}
# HMM 테스트 모듈 빌드 및 실행
# 커널 빌드 시 테스트 모듈 활성화
make menuconfig
# Kernel hacking → Memory Debugging → HMM test module
# CONFIG_TEST_HMM=m

# 모듈 로드
modprobe test_hmm

# 유저스페이스 테스트 실행
cd tools/testing/selftests/vm
make hmm-tests
./hmm-tests

# 개별 테스트 실행
./hmm-tests -t migrate_to_device
./hmm-tests -t snapshot
./hmm-tests -t fault

# 테스트 결과 확인
dmesg | tail -50
드라이버 개발 추천 순서:
  1. lib/test_hmm.c를 읽고 HMM API 사용 패턴 파악
  2. tools/testing/selftests/vm/hmm-tests.c로 유저스페이스 테스트 코드 참고
  3. drivers/gpu/drm/amd/amdkfd/kfd_svm.c에서 실전 드라이버 구현 확인
  4. 자체 드라이버에 HMM 통합 시 test_hmm의 테스트를 기반으로 검증

보안 고려사항

HMM은 GPU와 CPU 간 메모리를 공유하므로, 보안에 미치는 영향이 큽니다. 특히 device_private 페이지는 CPU에서 직접 접근할 수 없어 전통적인 메모리 보호 메커니즘이 적용되지 않는 영역이 생깁니다.

보안 위협설명커널 대응
GPU 메모리 유출이전 프로세스의 GPU 페이지가 새 프로세스에 노출page_free 콜백에서 VRAM 제로화 필수
DMA 버퍼 권한 위회DMA-BUF를 통해 다른 프로세스의 GPU 메모리 접근DMA-BUF fd 전달은 SCM_RIGHTS로만 제한
mmu_notifier 경쟁invalidate와 GPU 접근 사이의 시간 창invalidate_range_start에서 GPU 중지 필수
ptrace 제한device_private 페이지는 ptrace로 읽기 불가access_device_entry()에서 migrate_to_ram 강제
SELinux/AppArmorGPU 메모리 접근에 대한 MAC 정책 부재DRM render 노드 접근 제어(Access Control)로 간접 제한
사이드 채널GPU 페이지 폴트 타이밍으로 접근 패턴 유추GPU 드라이버의 폴트 통계 접근 제한
/* GPU 페이지 해제 시 보안 제로화 */
static void my_gpu_page_free(struct page *page)
{
    struct my_gpu_dev *gpu = page_to_gpu(page);

    /* VRAM 내용을 0으로 초기화 (정보 유출 방지) */
    gpu_memset_vram(gpu, page_to_vram_offset(page),
                    0, PAGE_SIZE);

    /* GPU 내부 TLB에서도 해당 매핑 제거 */
    gpu_tlb_invalidate(gpu, page);

    /* VRAM 페이지 풀에 반환 */
    gpu_free_vram_page(gpu, page);
}

/* mmu_notifier invalidate에서 GPU 접근 차단 */
static int my_gpu_invalidate_start(
    struct mmu_notifier *mn,
    const struct mmu_notifier_range *range)
{
    struct my_gpu_ctx *ctx = container_of(mn, struct my_gpu_ctx, mn);

    /* GPU 엔진 일시 정지 (invalidate와 접근 사이의 경쟁 방지) */
    gpu_engine_pause(ctx);

    /* 해당 범위의 GPU PTE 무효화 */
    gpu_pte_invalidate_range(ctx, range->start, range->end);

    /* GPU TLB 플러시 (shoot-down) */
    gpu_tlb_flush(ctx);

    return 0;
}
device_private 페이지와 코어 덤프(Core Dump): 프로세스가 코어 덤프를 생성할 때, device_private 페이지는 자동으로 CPU로 마이그레이션되어 코어 파일에 포함됩니다. 이는 GPU 메모리에 민감한 데이터(AI 모델 가중치, 암호화(Encryption) 키 등)가 있을 경우 의도치 않은 정보 유출 가능성이 있습니다. MADV_DONTDUMP으로 해당 VMA를 코어 덤프에서 제외하세요.

커널 소스 가이드

파일 / 디렉토리설명
mm/hmm.cHMM 핵심 구현 — hmm_range_fault(), mmu_notifier 통합
mm/migrate_device.cmigrate_vma_setup/pages/finalize() 구현
mm/memremap.cmemremap_pages(), ZONE_DEVICE 등록
include/linux/hmm.hHMM 공개 API — hmm_range, HMM_PFN_* 플래그
include/linux/memremap.hdev_pagemap, dev_pagemap_ops 구조체
include/linux/mmu_notifier.hmmu_notifier_ops, mmu_interval_notifier
drivers/gpu/drm/amd/amdkfd/kfd_svm.cAMD ROCm SVM 구현 (HMM 활용 참고)
drivers/gpu/drm/amd/amdkfd/kfd_migrate.cKFD GPU↔CPU 마이그레이션 로직
drivers/gpu/drm/nouveau/Nouveau HMM 구현 (NVIDIA 오픈소스)
lib/test_hmm.cHMM 테스트 모듈

커널 설정

# HMM 활성화 (GPU 드라이버 선택 시 자동으로 선택됨)
CONFIG_HMM_MIRROR=y             # HMM 미러 (mmu_notifier 기반)
CONFIG_DEVICE_PRIVATE=y         # ZONE_DEVICE device_private 지원
CONFIG_DEVICE_PUBLIC=y          # ZONE_DEVICE coherent 지원
CONFIG_MIGRATE=y                # 페이지 마이그레이션 지원
CONFIG_NUMA=y                   # NUMA 지원 (티어링에 필요)
CONFIG_NUMA_BALANCING=y         # 자동 NUMA 밸런싱

# AMD ROCm (HMM 주 사용자)
CONFIG_HSA_AMD=y
CONFIG_DRM_AMDGPU=y

# PMEM HMM
CONFIG_ZONE_DEVICE=y
CONFIG_FS_DAX=y

실전 사용 사례

대규모 언어 모델 (LLM) 추론

GPT-4급 LLM 모델은 파라미터가 수백 GB에 달하여 단일 GPU VRAM에 담을 수 없습니다. HMM을 활용하면 모델 파라미터를 CPU DDR + GPU VRAM에 분산 배치하고, 현재 추론 레이어에 필요한 파라미터만 GPU로 자동 마이그레이션할 수 있습니다.

/* LLM 추론에서 HMM 활용 패턴 (유저스페이스 관점) */
#include <hip/hip_runtime.h>

/* Unified Memory로 모델 파라미터 할당 (HMM 기반) */
float *model_weights;
hipMallocManaged(&model_weights, 200ULL * 1024 * 1024 * 1024);  /* 200 GB */

/* 모델 가중치를 CPU 메모리에 로드 (mmap + read) */
load_model_from_disk(model_weights, "model.safetensors");

/* GPU에서 추론 실행 — 필요한 레이어가 자동 마이그레이션 */
for (int layer = 0; layer < num_layers; layer++) {
    float *layer_weights = model_weights + layer_offsets[layer];
    size_t layer_size = layer_sizes[layer];

    /* 선호 위치 힌트: 이 레이어를 GPU로 프리페치 */
    hipMemPrefetchAsync(layer_weights, layer_size, gpu_device);

    /* GPU 커널 실행 (HMM이 나머지 페이지 폴트 처리) */
    transformer_layer_kernel<<<grid, block>>>(
        layer_weights, activations, layer);
}

/* 사용 완료 후 해제 — GPU와 CPU 페이지 모두 자동 정리 */
hipFree(model_weights);

과학 계산 (HPC)

기상 시뮬레이션, 분자 동역학 등 HPC 워크로드에서는 거대한 3D 격자(grid) 데이터를 CPU와 GPU가 교대로 처리합니다. HMM은 경계 교환(halo exchange) 패턴에서 명시적 복사 코드를 제거하고 코드 복잡도를 크게 줄여줍니다.

사용 사례데이터 규모HMM 이점주의사항
LLM 추론100~1000 GBVRAM 초과 모델 실행 가능레이어별 프리페치 필수
LLM 학습수십~수백 GBgradient 동기화 간소화thrashing 주의 (optimizer state)
이미지 처리수 GB제로 카피 파이프라인DMA-BUF 결합 시 최적
과학 계산수십~수백 GBhalo exchange 단순화경계 영역 thrashing 주의
데이터베이스수~수십 GBGPU 가속 쿼리인덱스 스캔 시 폴트 폭주 주의
비디오 인코딩수 GB카메라→GPU 제로 카피실시간(Real-time) 제약 (폴트 지연 예산)

카메라 → GPU → NPU 파이프라인

엣지 AI 디바이스에서는 카메라 센서 데이터가 GPU에서 전처리된 후 NPU에서 추론됩니다. HMM과 DMA-BUF를 결합하면 이 전체 파이프라인을 제로 카피로 구현할 수 있습니다.

카메라 → GPU → NPU 제로 카피 AI 파이프라인 카메라 ISP DMA-BUF export YUV 4:2:0 프레임 GPU (전처리) DMA-BUF import 리사이즈 + 정규화 NPU (추론) DMA-BUF import 객체 탐지 모델 CPU (후처리) HMM 자동 접근 결과 표시/전송 제로 카피 제로 카피 자동 이동 공유 물리 메모리 (DMA-BUF + HMM) IOMMU 매핑으로 모든 장치가 동일 물리 페이지 참조 → 복사 0회

HMM 통합 체크리스트

HMM 도입의 핵심 리스크는 CPU/GPU 페이지 테이블 불일치와 마이그레이션 경쟁 조건(Race Condition)입니다. 드라이버 통합 시 mmu_notifier 경로와 fault 경로를 함께 검증해야 합니다.

검사 항목질문점검 포인트
페이지 동기화CPU 매핑 변경이 GPU에 반영되는가?mmu_notifier invalidate 경로
마이그레이션CPU↔디바이스 이동 후 접근 권한이 맞는가?migrate_vma finalize 경로
fault 처리디바이스 fault에서 복구 가능한가?hmm_range_fault 결과 처리
회수/해제프로세스 종료 시 누수 없는가?dev_pagemap 참조 해제 검증
# HMM 관련 로그/코드 경로 점검
dmesg | grep -Ei "hmm|migrate_vma|device_private"
git grep -n "hmm_range_fault\\|migrate_vma" -- mm drivers/gpu

MEMORY_DEVICE_PRIVATE vs COHERENT

ZONE_DEVICE는 장치 메모리를 struct page로 관리하기 위한 특수 존이지만, 장치 메모리의 코히어런시 모델에 따라 세 가지 memory_type이 구분됩니다. 드라이버가 잘못된 타입을 선택하면 커널 패닉(Kernel Panic)이나 데이터 손상이 발생할 수 있으므로 정확한 이해가 필수입니다.

속성MEMORY_DEVICE_PRIVATEMEMORY_DEVICE_COHERENTMEMORY_DEVICE_GENERIC
대표 디바이스GPU VRAM (NVIDIA, AMD)CXL Type2 가속기DAX (persistent memory)
CPU 직접 접근불가 — fault시 마이그레이션 필수가능 — load/store 직접 수행가능 — mmap으로 매핑
코히어런시없음 (GPU 전용)하드웨어 코히어런트N/A (DAX)
migrate_to_ram필수 구현선택적해당 없음
PTE 인코딩pte_devmap + PRIVATE 마커pte_devmap + COHERENT 마커pte_devmap
GUP (get_user_pages)실패 → fault 트리거성공 (직접 접근)성공
NUMA 노드별도 NUMA 노드 할당별도 NUMA 노드 할당기존 노드 사용
커널 등장v4.16v5.14v4.8
MEMORY_DEVICE 타입별 CPU 접근 경로 CPU (load/store) MEMORY_DEVICE_PRIVATE GPU VRAM (접근 불가) MEMORY_DEVICE_COHERENT CXL Type2 (직접 접근) MEMORY_DEVICE_GENERIC DAX / pmem (직접 접근) fault → migrate_to_ram 직접 load/store mmap 직접 접근 migrate_to_ram() 콜백 VRAM → 시스템 메모리 복사 CPU 접근 가능 (시스템 RAM) pte_devmap + COHERENT 비트 pte_devmap (일반 DAX)
/* include/linux/memremap.h — ZONE_DEVICE 메모리 타입 정의 */
enum memory_type {
    MEMORY_DEVICE_PRIVATE = 1,  /* CPU 접근 불가, fault시 마이그레이션 */
    MEMORY_DEVICE_COHERENT,     /* CPU load/store 가능, 하드웨어 코히어런트 */
    MEMORY_DEVICE_FS_DAX,       /* DAX 파일시스템 (fsdax) */
    MEMORY_DEVICE_GENERIC,      /* 범용 DAX (devdax) */
    MEMORY_DEVICE_PCI_P2PDMA,  /* PCI peer-to-peer DMA */
};

/* dev_pagemap 구조체 — 장치 메모리 매핑 정보 */
struct dev_pagemap {
    struct vmem_altmap altmap;
    struct percpu_ref ref;        /* 참조 카운트 */
    struct completion done;       /* 해제 완료 대기 */
    enum memory_type type;         /* PRIVATE/COHERENT/... */
    unsigned int flags;
    const struct dev_pagemap_ops *ops; /* page_free, migrate_to_ram */
    void *owner;
    nr_range;
    union {
        struct range range;
        struct range ranges[0];
    };
};
코드 설명
  • MEMORY_DEVICE_PRIVATE: CPU가 직접 접근할 수 없는 장치 메모리. GPU VRAM이 대표적이며, CPU가 이 메모리를 읽으려 하면 migrate_to_ram 콜백이 호출됩니다.
  • MEMORY_DEVICE_COHERENT: CXL Type2 가속기처럼 CPU가 직접 load/store할 수 있는 장치 메모리. 하드웨어 캐시 코히어런시가 보장됩니다.
  • dev_pagemap.ops: 드라이버가 구현해야 하는 콜백 테이블. page_free(페이지 해제 시), migrate_to_ram(CPU 접근 시 마이그레이션)이 핵심입니다.
  • percpu_ref: 고성능 참조 카운팅으로, 페이지 해제 시점을 안전하게 추적합니다.
드라이버 선택 가이드: 장치가 CPU의 캐시 코히어런시 프로토콜(CXL.cache)에 참여하면 MEMORY_DEVICE_COHERENT, 그렇지 않으면 MEMORY_DEVICE_PRIVATE를 선택하세요. 잘못 선택하면 CPU 접근 시 데이터 오염이 발생합니다. CXL 메모리 문서에서 코히어런시 프로토콜 상세를 확인하세요.
/* PTE 인코딩 차이 — mm/memory.c */

/* PRIVATE: CPU fault → migrate_to_ram 트리거 */
static vm_fault_t handle_device_private_fault(struct vm_fault *vmf) {
    struct page *page = vmf->page;
    /* PRIVATE 페이지에 CPU가 접근: 시스템 RAM으로 마이그레이션 */
    return page->pgmap->ops->migrate_to_ram(vmf);
}

/* COHERENT: GUP 직접 성공, 마이그레이션 불필요 */
/* mm/gup.c — COHERENT 페이지는 GUP에서 바로 반환 */
if (is_device_coherent_page(page)) {
    /* 직접 접근 가능 — 마이그레이션 없이 page 반환 */
    return page;
}
PRIVATE 페이지와 GUP: get_user_pages()는 PRIVATE 페이지에 대해 실패합니다. 이는 sendfile(), splice(), O_DIRECT 등 GUP에 의존하는 I/O 경로에서 장치 메모리 페이지가 먼저 시스템 RAM으로 마이그레이션되어야 함을 의미합니다.

amdgpu vs nouveau HMM 구현 비교

업스트림 Linux에서 HMM을 가장 적극적으로 활용하는 두 GPU 드라이버는 AMD의 amdgpu(KFD)와 NVIDIA의 nouveau입니다. 두 드라이버의 HMM 통합 방식은 GPU 아키텍처 차이로 인해 상당히 다릅니다.

항목amdgpu (KFD SVM)nouveau (SVM)
핵심 소스drivers/gpu/drm/amd/amdkfd/kfd_svm.cdrivers/gpu/drm/nouveau/nouveau_svm.c
SVM 구조체struct svm_range (VMA별 interval tree)struct nouveau_svmm (mm 단위)
페이지 테이블GPU 전용 VMPT (4/5단계)GPU 전용 페이지 테이블 (3단계)
fault 처리XNACK 하드웨어 리트라이소프트웨어 fault → FIFO 스톱
마이그레이션svm_migrate_vma_to_vram()nouveau_dmem_migrate()
notifiermmu_interval_notifier (range별)mmu_notifier (mm 전체)
PrefetchSVM_ATTR_PREFETCH_LOC ioctl미지원
multi-GPUXGMI P2P 직접 마이그레이션NVLink 없음 (PCIe 경유)
Huge Page2MB VRAM 매핑 지원4KB 단위만
활발도매우 활발 (ROCm 상용)제한적 (reverse-eng 기반)
amdgpu vs nouveau — GPU Page Fault 처리 경로 amdgpu (KFD SVM) GPU XNACK fault SQ wavefront 일시정지 kfd_svm_fault() hmm_range_fault() GPU PT 갱신 XNACK retry → 재개 nouveau (SVM) GPU fault 인터럽트 FIFO 채널 중단 nouveau_svm_fault() hmm_range_fault() GPU PT 갱신 FIFO 재시작 XNACK: 하드웨어 리트라이 wavefront가 자동으로 재개됨 FIFO 중단: 소프트웨어 복구 채널 전체가 멈추고 재시작 필요
/* amdgpu KFD SVM fault 처리 — drivers/gpu/drm/amd/amdkfd/kfd_svm.c */
int svm_range_restore_pages(struct amdgpu_device *adev,
                           unsigned int pasid,
                           uint64_t addr, bool write_fault)
{
    struct svm_range *prange;
    struct hmm_range hmm_range;
    int r;

    /* 1. fault 주소로 svm_range 검색 (interval tree) */
    prange = svm_range_from_addr(svms, addr, NULL);

    /* 2. hmm_range_fault()로 CPU 페이지 테이블 스냅샷 */
    r = hmm_range_fault(&hmm_range);

    /* 3. GPU 페이지 테이블 갱신 */
    r = svm_range_map_to_gpus(prange, ...);

    /* 4. XNACK 하드웨어가 자동으로 wavefront 재개 */
    return r;
}
/* nouveau SVM fault 처리 — drivers/gpu/drm/nouveau/nouveau_svm.c */
static int nouveau_svm_fault(struct nvif_event *event, void *argv, u32 argc)
{
    struct nouveau_svm *svm = container_of(event, ...);
    struct hmm_range range;

    /* 1. fault 버퍼에서 fault 정보 추출 */
    nouveau_svm_fault_buffer_fetch(svm);

    /* 2. hmm_range_fault()로 CPU PTE 스냅샷 */
    ret = hmm_range_fault(&range);

    /* 3. GPU 페이지 테이블 갱신 */
    nouveau_range_fault(svmm, ...);

    /* 4. FIFO 채널 재시작 — 소프트웨어 복구 */
    nouveau_svm_fault_replay(svm);
    return NVIF_EVENT_KEEP;
}
성능 차이의 핵심: amdgpu의 XNACK 하드웨어 리트라이는 fault 처리 중에도 다른 wavefront가 계속 실행됩니다. 반면 nouveau의 FIFO 중단은 해당 채널의 모든 작업이 멈추므로 fault 빈도가 높을수록 성능 격차가 벌어집니다. ROCm 문서에서 AMD GPU의 XNACK 아키텍처를 상세히 확인하세요.

migrate_vma 상태 머신

migrate_vma는 CPU 메모리와 장치 메모리 간 페이지 마이그레이션을 수행하는 3단계 API입니다. 각 단계에서 src/dst 배열의 PFN 플래그를 통해 페이지 상태를 추적하며, 부분 실패 시에도 안전하게 롤백(Rollback)할 수 있습니다.

migrate_vma 3단계 상태 머신 Stage 1: Setup migrate_vma_setup() • VMA 유효성 검사 • src[] PFN 수집 • 원본 PTE 언맵 Stage 2: Pages 드라이버 DMA 복사 • dst[] 페이지 할당 • DMA 엔진으로 복사 • VALID 페이지만 처리 Stage 3: Finalize migrate_vma_finalize() • dst PTE 설치 • 원본 page 해제 • TLB flush src[]/dst[] PFN 플래그 인코딩 MIGRATE_PFN_VALID (유효) MIGRATE_PFN_MIGRATE (이동 승인) MIGRATE_PFN_WRITE (쓰기 가능) MIGRATE_PFN_DEVICE (장치 페이지) 실패 롤백 경로 dst[i] = 0 설정 → finalize에서 원본 PTE 복원, 원본 페이지 unmap 취소 성공: 모든 VALID 페이지가 dst에 정착, 원본 해제 완료
/* mm/migrate_device.c — migrate_vma 3단계 API 사용 패턴 */

/* 1단계: setup — src[] 배열에 원본 PFN 수집 */
struct migrate_vma args = {
    .vma        = vma,
    .start      = addr,
    .end        = addr + size,
    .src        = src_pfns,        /* 출력: 원본 PFN + 플래그 */
    .dst        = dst_pfns,        /* 입력: 대상 PFN + 플래그 */
    .pgmap_owner = drv->dev,       /* 자기 자신 소유 페이지 필터 */
    .flags      = MIGRATE_VMA_SELECT_SYSTEM, /* 시스템 RAM 페이지만 */
};
ret = migrate_vma_setup(&args);

/* 2단계: 드라이버가 DMA로 페이지 복사 */
for (i = 0; i < npages; i++) {
    if (!(src_pfns[i] & MIGRATE_PFN_MIGRATE))
        continue;  /* 이동 불가 페이지 스킵 */

    /* 장치 메모리에서 페이지 할당 */
    dst_pfns[i] = migrate_pfn(alloc_device_page(drv));
    dst_pfns[i] |= MIGRATE_PFN_VALID;

    /* DMA 엔진으로 src→dst 복사 */
    dma_copy(drv, migrate_pfn_to_page(src_pfns[i]),
                  migrate_pfn_to_page(dst_pfns[i]));
}

/* 3단계: finalize — 성공 페이지의 PTE 교체 */
migrate_vma_pages(&args);    /* dst PTE 설치 */
migrate_vma_finalize(&args); /* 원본 page 해제, TLB flush */
코드 설명
  • migrate_vma_setup(): VMA를 잠그고, 각 페이지의 PTE를 수집하여 src[]에 PFN과 플래그를 기록합니다. 이동 가능한 페이지는 MIGRATE_PFN_MIGRATE 플래그가 설정됩니다.
  • pgmap_owner: 자기 소유 디바이스 페이지를 건너뛰기 위한 필터. GPU-to-GPU 마이그레이션에서 소스와 대상이 같은 장치인 경우를 방지합니다.
  • MIGRATE_VMA_SELECT_SYSTEM: 시스템 RAM 페이지만 선택. MIGRATE_VMA_SELECT_DEVICE_PRIVATE를 쓰면 장치 메모리에서 시스템 RAM으로의 역방향 마이그레이션도 가능합니다.
  • migrate_vma_pages(): dst[i]가 유효한 페이지들만 CPU PTE를 새로운 장치 메모리 PTE로 교체합니다. dst[i] = 0인 페이지는 원본 PTE가 복원됩니다.
부분 실패 주의: migrate_vma는 일부 페이지가 실패해도 나머지 페이지는 이동합니다. 드라이버는 반드시 dst[] 배열을 확인하여 실패한 페이지(dst[i] == 0)에 대한 후처리를 해야 합니다. 실패한 장치 페이지는 put_page()로 해제해야 메모리 누수가 없습니다.

ZONE_DEVICE 페이지 생명주기

ZONE_DEVICE 페이지는 일반 시스템 RAM 페이지와 달리 장치 드라이버(Device Driver)가 생성하고 관리합니다. devm_memremap_pages()로 장치 메모리 영역에 대한 struct page 배열을 할당하며, 이후 드라이버가 제공하는 dev_pagemap_ops 콜백을 통해 생명주기를 제어합니다.

ZONE_DEVICE 페이지 생명주기 devm_memremap_pages() struct page[] 배열 생성 (vmemmap) Allocated (refcount=1) 드라이버가 할당, PTE 매핑 Mapped (refcount>1) 프로세스 PTE에 매핑됨 Migrating migrate_vma로 이동 중 page_free() 콜백 refcount→0 시 호출 드라이버 풀 반환 VRAM 프리리스트로 복귀 migrate_to_ram() CPU 접근 시 역마이그레이션 모듈 언로드: percpu_ref kill → completion wait → memunmap_pages()
/* ZONE_DEVICE 페이지 등록 — 드라이버 초기화 시 */
static const struct dev_pagemap_ops gpu_pagemap_ops = {
    .page_free      = gpu_devmem_page_free,    /* refcount→0 콜백 */
    .migrate_to_ram = gpu_devmem_fault,         /* CPU 접근 시 마이그레이션 */
};

int gpu_vram_init(struct gpu_device *gdev)
{
    struct dev_pagemap *pgmap;

    pgmap = devm_kzalloc(gdev->dev, sizeof(*pgmap), GFP_KERNEL);
    pgmap->type = MEMORY_DEVICE_PRIVATE;
    pgmap->range.start = gdev->vram_phys_base;
    pgmap->range.end = gdev->vram_phys_base + gdev->vram_size - 1;
    pgmap->nr_range = 1;
    pgmap->ops = &gpu_pagemap_ops;

    /* struct page 배열 생성 — vmemmap 영역에 할당 */
    gdev->vram_pages = devm_memremap_pages(gdev->dev, pgmap);
    if (IS_ERR(gdev->vram_pages))
        return PTR_ERR(gdev->vram_pages);

    /* 각 page의 zone = ZONE_DEVICE, pgmap 포인터 설정됨 */
    return 0;
}
코드 설명
  • devm_memremap_pages(): 물리 주소 범위에 대해 struct page 배열을 vmemmap에 할당합니다. 1GB VRAM이면 약 16MB의 struct page 메모리가 필요합니다 (64바이트 * 262,144 페이지).
  • page_free 콜백: 페이지 참조 카운트(Reference Count)가 0이 될 때 호출되어, 드라이버의 VRAM 프리리스트로 페이지를 반환합니다.
  • migrate_to_ram 콜백: CPU가 PRIVATE 페이지에 접근하면 do_swap_page()에서 이 콜백을 호출하여, 장치 메모리에서 시스템 RAM으로 데이터를 복사합니다.
memmap 메모리 비용: 16GB GPU VRAM을 ZONE_DEVICE로 등록하면 약 256MB의 시스템 RAM이 struct page 메타데이터에 사용됩니다. 이 비용은 VMEMMAP 영역에서 발생하며, /proc/zoneinfoDevice 존에서 확인할 수 있습니다. 대형 GPU(80GB A100 등)에서는 1.2GB 이상의 메타데이터가 필요합니다.

mmu_notifier 심층 분석

HMM의 핵심은 CPU 페이지 테이블 변경을 GPU에 동기화하는 것이며, 이를 위해 mmu_notifier 프레임워크를 사용합니다. v5.10에서 도입된 mmu_interval_notifier는 전체 mm이 아닌 특정 주소 범위만 추적하여 성능을 대폭 개선했습니다.

속성mmu_notifier (전통)mmu_interval_notifier (현대)
추적 범위전체 mm_struct특정 [start, end) 범위
콜백invalidate_range_start/endinvalidate (단일 콜백)
잠금(Lock)mmap_lock 필요interval tree + seq 카운터
사용자nouveau, KVMamdgpu KFD, HMM core
재시도수동 구현seq 카운터 기반 자동 감지
성능모든 VMA 변경에 호출관심 범위만 호출
mmu_interval_notifier — seq 카운터 재시도 프로토콜 시간 hmm_range_fault() seq = notifier->seq 저장 CPU PTE 스냅샷 invalidate() → seq++ (munmap/mprotect 등 PTE 변경) GPU PT 갱신 시도 seq 불일치! 재시도 (처음부터 다시) invalidate 콜백은 SRCU 읽기 측에서 보호 — 블로킹 불가, 빠른 무효화만
/* mm/hmm.c — hmm_range_fault()의 seq 카운터 기반 재시도 */
int hmm_range_fault(struct hmm_range *range)
{
    struct mmu_interval_notifier *notifier = range->notifier;
    unsigned long seq;
    int ret;

    do {
        /* 현재 seq 번호 저장 */
        seq = mmu_interval_read_begin(notifier);
        range->notifier_seq = seq;

        /* CPU 페이지 테이블 워크 */
        ret = hmm_vma_walk(range);
        if (ret)
            return ret;

        /* GPU PT 갱신 전 seq 재확인 */
    } while (mmu_interval_read_retry(notifier, seq));
    /* seq 변경 = 동시 무효화 발생 → 처음부터 재시도 */

    return 0;
}

/* mmu_interval_notifier 등록 — 드라이버 초기화 */
static const struct mmu_interval_notifier_ops gpu_notifier_ops = {
    .invalidate = gpu_invalidate_range,
};

/* 특정 범위만 추적 등록 */
mmu_interval_notifier_insert(&gpu->notifier, mm,
                              start, length, &gpu_notifier_ops);
코드 설명
  • mmu_interval_read_begin(): 현재 무효화(Invalidation) 시퀀스 번호를 반환합니다. 이후 CPU PTE를 읽는 동안 이 번호가 변경되지 않아야 합니다.
  • mmu_interval_read_retry(): 저장한 seq와 현재 seq를 비교합니다. 불일치하면 동시 무효화가 발생한 것이므로 처음부터 재시도합니다.
  • invalidate 콜백: CPU 쪽에서 munmap(), mprotect(), mremap() 등이 PTE를 변경할 때 호출됩니다. GPU 드라이버는 여기서 GPU 페이지 테이블의 해당 매핑을 무효화해야 합니다.
  • SRCU 보호: invalidate 콜백은 SRCU 읽기 측 임계 영역(Critical Section)에서 호출되므로, 블로킹 연산이 가능합니다 (RCU와 달리 sleep 허용).
중첩 무효화: invalidate() 콜백 실행 중에 또 다른 무효화가 발생할 수 있습니다. 이 경우 seq 카운터가 2 이상 증가하지만, hmm_range_fault()의 재시도 루프가 이를 자동으로 처리합니다. 드라이버는 중첩을 별도로 관리할 필요가 없습니다.
성능 영향: mmu_interval_notifier는 interval tree를 사용하므로 O(log N + M) 조회입니다 (N=전체 등록 수, M=겹치는 범위 수). 등록된 범위가 수천 개를 넘으면 무효화 콜백 비용이 증가합니다. AMD KFD는 svm_range를 적극적으로 병합하여 이 문제를 완화합니다.

XNACK 리트라이 메커니즘

AMD GPU의 XNACK(eXtra NACK)은 GPU가 유효하지 않은 페이지에 접근했을 때 하드웨어 수준에서 자동 리트라이하는 메커니즘입니다. 이 기능이 HMM 기반 SVM(Shared Virtual Memory)의 핵심 성능 요소입니다.

AMD XNACK GPU Page Fault 리트라이 흐름 Shader (CU/SQ) 메모리 접근 (load/store) XNACK — PTE 미스! wavefront 일시정지 (다른 wavefront 계속 실행) IH (Interrupt Handler) KFD fault handler hmm_range_fault() GPU PT 갱신 XNACK ACK 전송 wavefront 자동 재개 (하드웨어 리트라이) XNACK ON (amdgpu.noretry=0) SVM 필수 / oversubscription 가능 / ABI 제한 XNACK OFF (amdgpu.noretry=1) SVM 불가 / 사전 매핑 필수 / 최대 처리량
항목XNACK ONXNACK OFF
SVM 지원완전 지원불가 (사전 매핑만)
Oversubscription가능 (demand paging)불가
Fault 비용해당 wavefront만 정지전체 GPU 작업 중단
ABI 호환성XNACK용 바이너리 필요표준 바이너리
지원 GPUVega 이후 (gfx9+)모든 AMD GPU
파라미터amdgpu.noretry=0amdgpu.noretry=1 (기본값)
ROCm 사용HSA_XNACK=1HSA_XNACK=0
# XNACK 상태 확인
cat /sys/class/kfd/kfd/topology/nodes/*/properties | grep -i xnack

# XNACK ON으로 amdgpu 로드
modprobe amdgpu noretry=0

# ROCm 런타임에서 XNACK 모드 설정
export HSA_XNACK=1
./rocm_svm_application
XNACK ABI 주의: XNACK ON/OFF는 GPU 바이너리(ISA) 수준에서 호환되지 않습니다. XNACK ON에서 컴파일된 커널은 XNACK OFF GPU에서 실행할 수 없으며, 그 역도 마찬가지입니다. ROCm 6.0부터 --offload-arch=gfx90a:xnack+gfx90a:xnack-로 구분하여 빌드해야 합니다. ROCm 문서의 AMDGCN ISA 섹션을 참고하세요.

HMM 성능 프로파일링(Profiling)

HMM 기반 워크로드의 성능 병목은 주로 페이지 마이그레이션 빈도와 GPU fault 처리 지연에서 발생합니다. 커널 트레이싱 도구와 드라이버별 통계를 조합하여 문제를 진단할 수 있습니다.

# 1. ftrace로 HMM 관련 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/migrate/enable
echo 1 > /sys/kernel/debug/tracing/events/hmm/enable
cat /sys/kernel/debug/tracing/trace_pipe

# 2. bpftrace로 hmm_range_fault 지연 측정
bpftrace -e '
kprobe:hmm_range_fault { @start[tid] = nsecs; }
kretprobe:hmm_range_fault /@start[tid]/ {
    @latency_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}'

# 3. bpftrace로 migrate_vma 호출 빈도 추적
bpftrace -e '
kprobe:migrate_vma_setup {
    @migrate_count = count();
    @migrate_by_comm = count();
}
interval:s:5 { print(@migrate_count); print(@migrate_by_comm); }'

# 4. /proc/vmstat에서 마이그레이션 카운터 확인
grep -E "pgmigrate|thp_migration" /proc/vmstat

# 5. amdgpu debugfs 통계
cat /sys/kernel/debug/dri/0/amdgpu_vm_info
cat /sys/kernel/debug/dri/0/amdgpu_gpu_recover
도구측정 항목사용 시나리오
ftracehmm/migrate 이벤트 추적마이그레이션 경로 디버깅
bpftracehmm_range_fault 지연 히스토그램fault 처리 지연 분석
/proc/vmstatpgmigrate_success/fail 카운터마이그레이션 성공률 모니터링
amdgpu debugfsVM 정보, GPU 복구 이력GPU 드라이버 문제 진단
perf statdTLB/iTLB 미스TLB 압박으로 인한 fault 빈도 파악
rocm-smiVRAM 사용량, 온도, 처리량(Throughput)GPU 메모리 활용도 확인
/* 드라이버에서 마이그레이션 대역폭 측정 패턴 */
static void measure_migration_bw(struct gpu_device *gdev,
                                   unsigned long npages)
{
    ktime_t start, end;
    u64 elapsed_ns, bw_mbps;

    start = ktime_get();
    /* migrate_vma 수행 */
    do_migrate_to_vram(gdev, npages);
    end = ktime_get();

    elapsed_ns = ktime_to_ns(ktime_sub(end, start));
    bw_mbps = (npages * PAGE_SIZE * 1000ULL) / elapsed_ns;
    dev_info(gdev->dev, "Migration: %lu pages in %llu us = %llu MB/s\n",
             npages, elapsed_ns / 1000, bw_mbps);
}
성능 최적화 팁: 마이그레이션 대역폭이 PCIe 최대 대역폭(Gen4 x16 ≈ 25 GB/s)에 못 미치면 DMA 엔진 설정이나 페이지 크기(4KB vs 2MB)를 확인하세요. Huge Pages 마이그레이션은 TLB 미스를 줄이고 DMA 전송 효율을 높입니다.

대형 페이지(Huge Page) 마이그레이션

HMM은 2MB(PMD 레벨) 대형 페이지의 직접 마이그레이션을 지원합니다. THP(Transparent Huge Pages)를 분할(split)하지 않고 통째로 GPU VRAM으로 이동하면, TLB 엔트리 수 감소와 DMA 전송 효율 향상이라는 두 가지 이점을 얻습니다.

4KB vs 2MB 페이지 마이그레이션 비교 4KB 페이지 마이그레이션 (512회 반복) 4KB #1 4KB #2 4KB #3 ... #512 512x PTE 갱신 + 512x DMA setup + 512x TLB flush 2MB Huge Page 직접 마이그레이션 2MB compound page (단일 전송) 1x PMD 갱신 + 1x DMA setup + 1x TLB flush 성능 비교 (2MB 영역 마이그레이션) 4KB: ~500μs (PTE overhead 지배적) 2MB: ~50μs (DMA 전송만, 10x 빠름) GPU PT: 512개 PTE 엔트리 필요 GPU PT: 1개 PDE (2MB) 엔트리만 필요
/* mm/migrate_device.c — THP 직접 마이그레이션 지원 */

/* migrate_vma_collect_pmd() — PMD 레벨 compound page 처리 */
static int migrate_vma_collect_pmd(pmd_t *pmdp, ...)
{
    if (pmd_trans_huge(*pmdp)) {
        /* THP 발견 — 분할 없이 직접 수집 */
        struct page *page = pmd_page(*pmdp);
        unsigned long pfn = page_to_pfn(page);

        for (i = 0; i < HPAGE_PMD_NR; i++) {
            src_pfns[i] = migrate_pfn(pfn + i) |
                          MIGRATE_PFN_VALID | MIGRATE_PFN_MIGRATE;
        }
        /* PMD를 통째로 언맵 */
        pmdp_invalidate(vma, addr, pmdp);
        return 0;
    }
    /* 일반 4KB PTE 수집 경로 */
    ...
}
항목4KB 페이지2MB Huge Page
TLB 엔트리 수 (2MB 영역)512개1개
DMA 디스크립터512개 (scatter-gather)1개 (연속 전송)
PTE 갱신 횟수512회1회 (PMD)
마이그레이션 지연높음 (오버헤드 지배적)낮음 (데이터 전송 지배적)
GPU 페이지 테이블PTE 레벨PDE 레벨 (2MB 매핑)
제약없음연속 물리 메모리 필요, 부분 마이그레이션 불가
amdgpu 2MB 매핑: AMD KFD는 svm_range가 2MB 정렬되고 전체가 동일 NUMA 노드에 있으면 자동으로 2MB GPU PDE 매핑을 생성합니다. 이는 AI/ML 워크로드에서 대용량 텐서 접근 시 GPU TLB 미스를 대폭 줄입니다. Huge Pages 문서에서 THP 설정 방법을 확인하세요.
Huge Page 마이그레이션 제약: 2MB 페이지는 반드시 전체가 마이그레이션됩니다. 일부만 GPU에서 필요한 경우에도 2MB 전체가 이동하므로, 작은 영역을 빈번히 접근하는 패턴에서는 오히려 비효율적일 수 있습니다. MADV_NOHUGEPAGE로 특정 VMA에서 THP를 비활성화할 수 있습니다.

Intel Xe GPU HMM 통합

Intel의 차세대 GPU 드라이버 Xe는 기존 i915를 대체하며, HMM 기반 SVM(Shared Virtual Memory)을 핵심 기능으로 지원합니다. Xe는 xe_vmxe_bo(Buffer Object) 구조를 사용하며, EU(Execution Unit)의 하드웨어 페이지 폴트를 통해 demand paging을 구현합니다.

Intel Xe GPU — HMM SVM 아키텍처 사용자 프로세스 (가상 주소 공간) xe_vm (VM 관리) xe_bo (Buffer Object) HMM / mmu_notifier migrate_vma Intel Xe GPU Hardware EU (Exec Units) SIMD / Vector PPGTT (GPU PT) 4-level page table Page Fault Unit HW fault → GuC → KMD LMEM Local Memory Xe vs i915 Xe: HMM 네이티브 / i915: GEM 기반 (HMM 미지원) 지원 GPU Arc (DG2), Data Center GPU Max (PVC), Lunar Lake+
항목Intel XeIntel i915
HMM 지원네이티브 SVM미지원 (GEM 기반)
VM 구조xe_vm (HMM 통합)i915_address_space
Buffer Objectxe_bo (TTM 기반)i915_gem_object
GPU 페이지 테이블PPGTT 4레벨PPGTT 4레벨
Page FaultGuC 매개 HW fault미지원
Local MemoryLMEM (DEVICE_PRIVATE)LMEM (GEM 관리)
스케줄러(Scheduler)GuC 기반execlists / GuC
상태v6.8+ 업스트림유지보수 모드
/* drivers/gpu/drm/xe/xe_svm.c — Xe SVM fault 처리 */
static int xe_svm_fault(struct xe_vm *vm, struct xe_vma *vma,
                        u64 fault_addr, bool is_write)
{
    struct hmm_range range = {
        .notifier = &vma->notifier,
        .start    = fault_addr & PAGE_MASK,
        .end      = (fault_addr & PAGE_MASK) + PAGE_SIZE,
        .hmm_pfns = pfns,
        .default_flags = HMM_PFN_REQ_FAULT,
    };

    if (is_write)
        range.default_flags |= HMM_PFN_REQ_WRITE;

    /* HMM을 통해 CPU PTE 스냅샷 */
    ret = hmm_range_fault(&range);
    if (ret)
        return ret;

    /* PPGTT에 매핑 설치 */
    ret = xe_pt_update(vm, vma, pfns, ...);

    return ret;
}
코드 설명
  • xe_vm: Xe 드라이버의 VM(가상 메모리) 관리 구조체. HMM의 mmu_interval_notifier를 직접 내장합니다.
  • xe_bo: Xe의 Buffer Object로, TTM(Translation Table Manager) 위에 구축됩니다. LMEM(Local Memory)과 시스템 RAM 간 이동을 관리합니다.
  • GuC: GPU의 마이크로컨트롤러로, EU에서 발생한 페이지 폴트를 커널 드라이버(KMD)에 전달하는 중간 매개체입니다.
  • PPGTT: Per-Process Graphics Translation Table. 프로세스별 GPU 페이지 테이블로, CPU의 CR3에 해당합니다.
Xe와 oneAPI: Intel의 oneAPI/Level Zero 런타임은 Xe 드라이버의 SVM 기능을 활용하여 zeMemAllocShared()로 CPU-GPU 공유 메모리를 할당합니다. 내부적으로 HMM의 hmm_range_fault()migrate_vma를 사용합니다. GPU 서브시스템 문서에서 DRM/KMS 전체 구조를 확인하세요.

CXL Type2 HMM 연동

CXL(Compute Express Link) Type2 디바이스는 가속기(GPU, FPGA, AI 칩)에 부착된 메모리를 호스트 CPU와 공유하는 장치입니다. HMM의 MEMORY_DEVICE_COHERENT 타입을 사용하여 CPU가 직접 load/store할 수 있는 장치 메모리를 관리하며, CXL.cache/CXL.mem 프로토콜로 하드웨어 코히어런시를 보장합니다.

CXL Type2 디바이스 — HMM DEVICE_COHERENT 연동 호스트 CPU LLC (Last Level Cache) + DDR CXL.cache + CXL.mem CXL Type2 가속기 컴퓨트 엔진 + Device-Attached Memory (MEMORY_DEVICE_COHERENT) Linux 커널 스택 CXL 드라이버 (cxl_mem) HMM (mm/hmm.c) ZONE_DEVICE (COHERENT) NUMA 노드 등록 HDM 디코더 → 물리 주소 매핑 Host-Bias 모드 CPU가 캐시 주도권 보유 디바이스 접근 시 스누프 필요 Device-Bias 모드 디바이스가 캐시 주도권 보유 CPU 접근 시 코히어런시 요청 Shared-Bias 모드 양쪽 모두 캐시 가능 스누프 트래픽 증가 AI 가속기 메모리 풀: Device-Bias로 학습, Host-Bias로 데이터 로드 → 동적 전환
CXL 타입장치 예시ZONE_DEVICE 타입HMM 활용
Type1SmartNIC, 네트워크 가속기해당 없음CXL.cache만 (메모리 없음)
Type2GPU, FPGA, AI 가속기MEMORY_DEVICE_COHERENTHMM 핵심 대상 (SVM)
Type3CXL 메모리 확장기MEMORY_DEVICE_GENERIC / DAXNUMA 노드로 관리
/* CXL Type2 드라이버에서 DEVICE_COHERENT 등록 */
static int cxl_type2_mem_init(struct cxl_dev *cxldev)
{
    struct dev_pagemap *pgmap;

    pgmap = devm_kzalloc(&cxldev->dev, sizeof(*pgmap), GFP_KERNEL);
    pgmap->type = MEMORY_DEVICE_COHERENT;  /* CPU 직접 접근 가능 */
    pgmap->range.start = cxldev->mem_base;
    pgmap->range.end = cxldev->mem_base + cxldev->mem_size - 1;
    pgmap->ops = &cxl_type2_pagemap_ops;

    /* NUMA 노드 등록 — 별도 메모리 티어 */
    pgmap->nr_range = 1;

    /* HDM 디코더 설정 — 물리 주소 범위 매핑 */
    cxl_hdm_decoder_setup(cxldev, pgmap->range.start,
                          pgmap->range.end - pgmap->range.start + 1);

    /* struct page 배열 생성 */
    cxldev->pages = devm_memremap_pages(&cxldev->dev, pgmap);
    if (IS_ERR(cxldev->pages))
        return PTR_ERR(cxldev->pages);

    /* NUMA 메모리 티어에 등록 (demotion 대상) */
    register_memory_tier(cxldev->numa_node, MEMORY_TIER_DEFAULT_RANK - 1);

    return 0;
}
코드 설명
  • MEMORY_DEVICE_COHERENT: CXL Type2 메모리는 CXL.cache 프로토콜로 CPU 캐시 코히어런시가 보장되므로, CPU가 직접 load/store할 수 있습니다.
  • HDM 디코더: Host-managed Device Memory 디코더로, CXL 장치의 물리 주소 범위를 호스트의 물리 주소 공간에 매핑합니다.
  • register_memory_tier(): CXL 메모리를 NUMA 메모리 티어에 등록합니다. DDR보다 낮은 랭크를 설정하면 demotion 대상이 됩니다.
  • Bias 모드: Host-Bias에서는 CPU가 캐시 라인(Cache Line) 소유권을 가지고, Device-Bias에서는 가속기가 소유권을 가집니다. 워크로드에 따라 동적으로 전환하여 스누프 트래픽을 최소화합니다.
AI 워크로드 최적화: AI 학습 시 텐서 데이터를 Device-Bias로 설정하면 가속기가 스누프 없이 직접 접근합니다. 데이터 로딩 단계에서는 Host-Bias로 전환하여 CPU가 효율적으로 데이터를 채웁니다. 이 동적 전환은 마이그레이션 없이 bias 비트만 변경하므로 오버헤드가 매우 적습니다. CXL 메모리 문서에서 CXL 프로토콜 상세를, NUMA 문서에서 메모리 티어링을 확인하세요.
HDM 디코더 제한: CXL 3.0에서도 HDM 디코더 수는 제한적입니다 (보통 8~16개). 대규모 CXL 패브릭에서 여러 Type2 디바이스를 사용하면 디코더 부족으로 동시 매핑 가능한 메모리 범위가 제한될 수 있습니다. 커널의 CXL 리전 관리자가 디코더를 동적으로 할당/해제하여 이 제약을 완화합니다.
COHERENT vs PRIVATE 선택 기준: CXL Type2는 반드시 MEMORY_DEVICE_COHERENT를 사용해야 합니다. 만약 장치가 CXL.cache를 지원하지 않는다면(예: PCIe 전용 GPU), MEMORY_DEVICE_PRIVATE를 사용하고 migrate_to_ram 콜백을 구현해야 합니다. 두 타입을 혼동하면 데이터 손상이 발생합니다.

COW/fork()/exec과 HMM

fork() 시스템 콜은 부모 프로세스의 페이지 테이블(Page Table)을 자식에게 복사합니다. device_private PTE는 스왑(Swap) 유사 엔트리로 인코딩되어 있으므로, copy_page_range()가 이를 그대로 자식 페이지 테이블에 복제합니다. 이후 자식(또는 부모)이 해당 주소에 접근하면 migrate_to_ram이 호출되어 GPU에서 CPU로 페이지가 복귀하고, 이어서 COW(Copy-on-Write) 분리가 발생합니다.

Parent Process device_private PTE fork() copy_page_range() copy_nonpresent_pte() swap PTE 그대로 복사 Child Process device_private PTE (동일) mmu_notifier dup_mmap() 중 호출 → GPU 드라이버 통지 자식 접근! migrate_to_ram 콜백 GPU → CPU DMA 전송 COW (Copy-on-Write) 부모/자식 독립 페이지 분리 exec() 경로 mm teardown → PTE 전부 폐기 exec() mmu_notifier_release GPU 자원 정리 (VRAM 해제) 요약: • fork(): device_private PTE 그대로 복사 → 접근 시 migrate_to_ram + COW • exec(): mm 해체 → mmu_notifier_release → GPU 자원 정리 • device_private PTE는 swap-like 엔트리로 인코딩 → copy_nonpresent_pte()에서 처리

device_private PTE는 커널 내부에서 스왑 엔트리와 유사한 형식으로 인코딩됩니다. fork()copy_page_range()copy_nonpresent_pte() 경로에서 이 엔트리를 그대로 자식 프로세스의 페이지 테이블에 복사합니다. 이때 GPU 드라이버에는 mmu_notifier를 통해 dup_mmap() 이벤트가 통지됩니다.

자식 프로세스(또는 fork 이후 부모)가 해당 가상 주소에 접근하면 다음 순서로 처리됩니다:

  1. CPU 페이지 폴트 발생 → swap-like PTE 감지
  2. do_swap_page()device_private 타입 확인
  3. migrate_to_ram() 콜백 호출 → GPU에서 CPU로 DMA 전송
  4. 페이지가 CPU에 도착 → COW 처리 (부모/자식 독립 복사본 생성)

exec()의 경우에는 기존 mm_struct를 완전히 해체하므로, 모든 device_private 엔트리가 폐기되고 mmu_notifier_release()를 통해 GPU 드라이버가 VRAM 자원을 정리합니다.

커널 경로 코드

/* mm/memory.c — fork() 시 device_private PTE 처리 */
static inline int
copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
                   pte_t *dst_pte, pte_t *src_pte,
                   struct vm_area_struct *src_vma,
                   unsigned long addr, int *rss)
{
    swp_entry_t entry = pte_to_swp_entry(pte);

    if (is_device_private_entry(entry)) {
        struct page *page = pfn_swap_entry_to_page(entry);

        /*
         * device_private 페이지의 참조 카운트 증가.
         * fork 시 부모와 자식 모두 같은 device page를 참조.
         * 나중에 접근 시 migrate_to_ram → COW 순서로 분리됨.
         */
        get_page(page);
        rss[MM_ANONPAGES]++;

        /* 자식 PTE에 동일한 device_private 엔트리 설정 */
        set_pte_at(dst_mm, addr, dst_pte, pte);
        return 0;
    }
    /* ... 다른 swap 엔트리 처리 ... */
}
코드 설명
  • is_device_private_entry(): PTE가 device_private 스왑 엔트리인지 확인합니다.
  • get_page(): 참조 카운트를 증가시켜 부모와 자식이 동일한 GPU 페이지를 공유합니다.
  • rss[MM_ANONPAGES]++: device_private 페이지도 익명 페이지 RSS에 카운트됩니다.
  • 실제 데이터 분리는 접근 시점까지 지연(Lazy)되어, migrate_to_ram + COW로 처리됩니다.
마이그레이션 폭풍 주의: GPU에서 대량의 메모리를 사용 중인 프로세스가 fork()를 호출하면, 자식 프로세스의 모든 메모리 접근이 migrate_to_ram을 트리거합니다. 수 GB의 GPU 메모리가 PCIe를 통해 CPU로 역류하면서 심각한 성능 저하가 발생할 수 있습니다. GPU 워크로드가 활성화된 상태에서 fork()를 피하거나, fork() 직후 즉시 exec()를 호출하여 mm을 교체하는 것이 권장됩니다. Python multiprocessing의 fork 방식 대신 spawn 또는 forkserver 방식을 사용하세요.

Swap/OOM과 HMM 상호작용

device_private 페이지는 이미 GPU VRAM에 존재하므로 디스크(Disk)로의 스왑 아웃 대상이 아닙니다. 커널 관점에서 이 페이지들은 "이미 스왑된" 상태와 유사합니다. 그러나 OOM Killer와 메모리 압박(Memory Pressure) 상황에서 device_private 페이지가 프로세스의 RSS에 포함되어 있으므로, 이들의 처리 방식을 정확히 이해하는 것이 중요합니다.

동작 일반 익명 페이지 device_private 페이지
Swap Out 디스크/zswap으로 기록 불가 — 이미 GPU에 있음 (스왑 불필요)
OOM Kill 프로세스 종료, 페이지 해제 mmu_notifier_release → GPU 드라이버 정리
OOM Reaper 페이지 테이블 해체, 페이지 해제 device_private PTE 건너뜀 (GPU 드라이버가 정리)
RSS 계산 MM_ANONPAGES에 포함 MM_ANONPAGES에 포함 (동일 카운터)
mlock 스왑 아웃 방지, RAM에 고정 효과 없음 — device_private는 스왑 대상 아님
madvise(DONTNEED) 즉시 해제 device_private PTE 해제 + mmu_notifier 통지
madvise(PAGEOUT) 스왑 아웃 힌트 무시됨 — reclaim 대상 아님
NUMA balancing NUMA 힌트 폴트로 마이그레이션 device_private에는 적용 안 됨

OOM Reaper와 device_private

OOM Killer가 프로세스를 선택하면, OOM Reaper가 해당 프로세스의 페이지 테이블을 순회하며 가능한 한 빨리 메모리를 회수합니다. device_private PTE를 만나면 직접 해제하지 않고 건너뜁니다 — GPU 드라이버만이 VRAM 페이지를 안전하게 해제할 수 있기 때문입니다.

/* mm/oom_kill.c — OOM reaper의 device_private 처리 */
static bool __oom_reap_task_mm(struct mm_struct *mm)
{
    struct vm_area_struct *vma;
    bool ret = true;

    /*
     * mmu_notifier_release()가 먼저 호출되어
     * GPU 드라이버에게 정리 기회를 줌
     */
    mmu_notifier_release(mm);

    VMA_ITERATOR(vmi, mm, 0);
    for_each_vma(vmi, vma) {
        if (vma->vm_flags & (VM_HUGETLB | VM_PFNMAP))
            continue;

        /*
         * unmap_page_range()가 페이지 테이블 순회.
         * device_private PTE는 zap_pte_range()에서
         * 참조 카운트를 감소시키고 PTE를 클리어.
         * 실제 VRAM 해제는 page_free 콜백에서 처리.
         */
        unmap_page_range(&tlb, vma, vma->vm_start,
                        vma->vm_end, &details);
    }

    return ret;
}
코드 설명
  • mmu_notifier_release(): OOM 과정에서 가장 먼저 호출됩니다. GPU 드라이버는 이 콜백에서 GPU 페이지 테이블을 무효화하고, 진행 중인 GPU 작업을 중단합니다.
  • zap_pte_range(): device_private PTE를 만나면 pfn_swap_entry_to_page()로 page를 찾고, put_page()로 참조 카운트를 감소시킵니다.
  • 참조 카운트가 0이 되면 dev_pagemap_ops.page_free() 콜백이 호출되어 GPU 드라이버가 VRAM 페이지를 자유 풀에 반환합니다.

hmm_range_fault()로 GPU 페이지 테이블에 매핑된 CPU 페이지가 스왑 아웃 대상이 되면, 커널은 먼저 mmu_notifier invalidate 콜백을 호출합니다. GPU 드라이버는 이 콜백에서 해당 범위의 GPU 페이지 테이블 엔트리를 무효화하고, 다음 GPU 접근 시 hmm_range_fault()를 다시 호출하여 최신 매핑을 얻어야 합니다.

mlock과 device_private: mlock()은 페이지를 RAM에 고정하여 스왑 아웃을 방지하는 시스템 콜입니다. 그러나 device_private 페이지는 애초에 스왑 대상이 아니므로 mlock()이 아무 효과가 없습니다. GPU 메모리를 "고정"하려면 GPU 드라이버의 BO(Buffer Object) 핀(Pin) API를 사용해야 합니다.

HMM 메모리 일관성 모델 (Memory Ordering)

HMM은 CPU와 GPU 사이에 최종 일관성(Eventual Consistency) 모델을 제공합니다. 하드웨어 캐시 코히어런시(Cache Coherency)가 아닌 소프트웨어 기반 동기화(mmu_notifier + migrate_vma)로 일관성을 유지합니다. MEMORY_DEVICE_COHERENT(CXL)만이 하드웨어 캐시 코히어런시를 제공합니다.

PRIVATE: 소프트웨어 동기화 (Eventual Consistency) CPU 쓰기 mmu_notifier invalidate_range GPU 페이지 테이블 무효화 (invalidate) GPU re-fault 최신 데이터 확인 GPU 쓰기 GPU Fence 완료 DMA 완료 보장 CPU 접근 시 migrate_to_ram 호출 CPU에서 확인 DMA 데이터 도착 COHERENT (CXL): 하드웨어 캐시 코히어런시 CPU 쓰기 CXL.cache 프로토콜 하드웨어 스누프 장치에서 즉시 확인 마이그레이션 불필요 시퀀스 번호 메커니즘 (hmm_range_fault 원자성 보장) mmu_interval_read_begin seq 번호 획득 페이지 테이블 순회 mmu_interval_read_retry → 변경 감지 invalidate 발생 시 seq 번호 변경 → retry 감지 → -EBUSY 반환 → 드라이버 재시도
특성 PRIVATE (GPU VRAM) COHERENT (CXL Type2)
일관성 모델 최종 일관성 (Eventual Consistency) 캐시 코히어런시 (Cache Coherency)
CPU 쓰기 → GPU 가시성 mmu_notifier invalidate + GPU re-fault 필요 하드웨어 스누프로 즉시 반영
GPU 쓰기 → CPU 가시성 migrate_to_ram (DMA 전송) 필요 하드웨어 스누프로 즉시 반영
동기화 오버헤드 높음 (마이그레이션 + TLB flush) 낮음 (캐시 라인 수준 스누프)
대역폭 PCIe/NVLink DMA 속도 CXL.cache 속도 (CPU 캐시 레이턴시)
원자성 보장 시퀀스 번호 (mmu_interval_read_begin/retry) 하드웨어 원자적 연산 가능
GPU 쓰기 가시성 창(Visibility Window): MEMORY_DEVICE_PRIVATE 모드에서 GPU가 데이터를 쓴 직후부터 migrate_to_ram이 완료될 때까지 CPU는 최신 데이터를 볼 수 없습니다. GPU 드라이버는 migrate_to_ram 구현에서 반드시 GPU DMA가 완전히 완료된 후에 함수를 반환해야 합니다 (GPU fence/barrier 사용). 이 규칙을 어기면 CPU가 불완전한 데이터를 읽는 데이터 손상(Data Corruption)이 발생합니다.

hmm_range_fault 내부 처리 상세

hmm_range_fault()는 GPU 드라이버가 CPU 페이지 테이블의 현재 상태를 GPU 페이지 테이블에 반영하기 위해 호출하는 핵심 함수입니다. 내부적으로 복잡한 페이지 테이블 워크(Page Table Walk)와 폴트 처리를 수행합니다.

1. mmu_interval_read_begin() seq 번호 획득 2. mmap_read_lock(mm) 3. walk_page_range(hmm_walk_ops) 4. 각 PTE 검사 (hmm_vma_walk_pmd) present (정상) none (미할당) swap / device_private read-only (쓰기 요청) PFN + flags 수집 REQ_FAULT? Yes handle_mm_fault() device_private PFN 수집 REQ_WRITE? Yes handle_mm_fault(COW) 7. hmm_pfns[] 배열에 PFN + 플래그 저장 8. mmap_read_unlock(mm) 9. read_retry? 변경 → -EBUSY

hmm_range_fault()의 핵심 내부 콜백은 hmm_vma_walk_pmd()입니다. 이 함수가 PMD 단위로 페이지 테이블을 순회하며 각 PTE의 상태에 따라 적절한 처리를 수행합니다.

/* mm/hmm.c — hmm_vma_walk_pmd 내부 로직 (간략화) */
static int hmm_vma_walk_pmd(pmd_t *pmdp, unsigned long start,
                            unsigned long end, struct mm_walk *walk)
{
    struct hmm_vma_walk *hmm_vma_walk = walk->private;
    struct hmm_range *range = hmm_vma_walk->range;
    unsigned long *hmm_pfns = range->hmm_pfns;
    pte_t *ptep, pte;
    spinlock_t *ptl;

    ptep = pte_offset_map_lock(walk->mm, pmdp, start, &ptl);

    for (; start < end; start += PAGE_SIZE, ptep++, hmm_pfns++) {
        pte = ptep_get(ptep);

        if (pte_none(pte)) {
            /* 매핑 없음 — REQ_FAULT이면 demand fault 발생 */
            if (*hmm_pfns & HMM_PFN_REQ_FAULT) {
                pte_unmap_unlock(ptep, ptl);
                return hmm_vma_fault(start, end, walk);
            }
            *hmm_pfns = 0;  /* HMM_PFN_NONE */
            continue;
        }

        if (!pte_present(pte)) {
            swp_entry_t entry = pte_to_swp_entry(pte);
            if (is_device_private_entry(entry)) {
                /* device_private → PFN 수집, GPU가 직접 접근 가능 */
                *hmm_pfns = pfn_swap_entry_to_page(entry)
                          | HMM_PFN_VALID;
                if (is_writable_device_private_entry(entry))
                    *hmm_pfns |= HMM_PFN_WRITE;
                continue;
            }
            /* 일반 swap — REQ_FAULT이면 swap-in 트리거 */
            if (*hmm_pfns & HMM_PFN_REQ_FAULT) {
                pte_unmap_unlock(ptep, ptl);
                return hmm_vma_fault(start, end, walk);
            }
            *hmm_pfns = 0;
            continue;
        }

        /* present PTE — read-only인데 WRITE 요청이면 COW */
        if ((*hmm_pfns & HMM_PFN_REQ_WRITE) && !pte_write(pte)) {
            pte_unmap_unlock(ptep, ptl);
            return hmm_vma_fault(start, end, walk);
        }

        /* PFN과 플래그 수집 */
        *hmm_pfns = pte_pfn(pte) | HMM_PFN_VALID;
        if (pte_write(pte))
            *hmm_pfns |= HMM_PFN_WRITE;
    }

    pte_unmap_unlock(ptep, ptl);
    return 0;
}
코드 설명
  • pte_offset_map_lock(): PMD 내의 PTE 배열에 대한 포인터를 얻고 페이지 테이블 락을 획득합니다.
  • pte_none(): PTE가 비어 있으면(아직 매핑되지 않은 주소), HMM_PFN_REQ_FAULT 플래그가 설정된 경우에만 demand paging을 트리거합니다.
  • is_device_private_entry(): 이미 GPU에 있는 페이지이므로 해당 PFN을 직접 수집합니다. GPU 드라이버는 이 PFN이 자신의 VRAM 주소임을 알고 있습니다.
  • hmm_vma_fault(): 내부에서 handle_mm_fault()를 호출하여 demand allocation이나 COW를 트리거합니다.
  • HMM_PFN_VALID | HMM_PFN_WRITE: GPU 드라이버가 이 플래그를 확인하여 GPU 페이지 테이블에 읽기/쓰기 권한을 적절히 설정합니다.
성능 팁 — 배치 처리: hmm_range_fault()mmap_read_lock을 획득하므로 호출 횟수를 최소화하는 것이 중요합니다. GPU 드라이버는 가능한 한 넓은 범위를 한 번의 hmm_range_fault() 호출로 처리하여 락 오버헤드를 분산시켜야 합니다. AMD amdgpu 드라이버의 경우 최대 512MB 범위를 한 번에 처리합니다.

HMM과 GUP/pin_user_pages 상호작용

get_user_pages()(GUP)는 사용자 공간 메모리 페이지의 물리 주소를 얻어 커널이나 DMA가 직접 접근할 수 있게 하는 함수입니다. 그러나 device_private 페이지는 CPU가 직접 접근할 수 없으므로, GUP는 이 페이지를 먼저 CPU로 마이그레이션한 후에야 핀(Pin)할 수 있습니다.

get_user_pages() 호출 GUP-fast 경로 시도 present PTE device_private 페이지 핀 성공 (빠른 경로) GUP-fast 실패 → 느린 경로 GUP slow: faultin_page() do_swap_page → migrate_to_ram GPU → CPU DMA 전송 CPU 페이지 핀 성공 FOLL_LONGTERM (RDMA, O_DIRECT 등) device_private이면 반드시 migrate_to_ram 후 장기 핀 GUP을 사용하는 주요 경로: O_DIRECT read/write · sendfile · splice · RDMA (ibv_reg_mr) · VFIO (DMA map) · io_uring fixed buffers · KVM EPT 매핑

GUP가 device_private 페이지를 만나는 주요 경로는 다음과 같습니다:

  1. GUP-fast (락 없는 빠른 경로): PTE가 present가 아니므로 즉시 실패하고 느린 경로로 전환
  2. GUP-slow: faultin_page()handle_mm_fault()do_swap_page()
  3. do_swap_page()에서 device_private 감지 → migrate_to_ram() 호출
  4. GPU에서 CPU로 DMA 전송 완료 후 일반 CPU 페이지로 변환
  5. GUP가 해당 CPU 페이지를 핀(참조 카운트 증가)
ZONE_DEVICE 유형 GUP-fast GUP-slow 동작
MEMORY_DEVICE_PRIVATE 실패 마이그레이션 후 핀 migrate_to_ram → CPU 페이지 핀
MEMORY_DEVICE_COHERENT 성공 가능 성공 마이그레이션 없이 직접 핀 (CPU 접근 가능)
MEMORY_DEVICE_FS_DAX 성공 가능 성공 LONGTERM 핀 시 추가 제약 있음
MEMORY_DEVICE_GENERIC 실패 드라이버 의존 일반적으로 DMA 전용
RDMA + HMM "핀 마이그레이션 폭풍": RDMA가 ibv_reg_mr()로 메모리를 등록하면 해당 페이지가 CPU RAM에 장기 핀됩니다. GPU가 이 범위를 hmm_range_fault()로 접근하면 해당 페이지가 GPU로 마이그레이션되지만, RDMA 핀이 유지되므로 실제로는 마이그레이션이 실패하거나, 마이그레이션 후 RDMA 측에서 다시 CPU로 끌어와 핀합니다. 이 순환이 반복되면 성능이 심각하게 저하됩니다. GPU 워크로드와 RDMA 버퍼는 가급적 다른 가상 주소 범위를 사용하세요.

HMM 페이지 상태 전이도

HMM에서 관리하는 페이지는 다양한 상태 사이를 전이합니다. 아래 다이어그램은 하나의 가상 주소에 대응하는 페이지가 거칠 수 있는 모든 상태와 전이 조건을 보여줍니다.

미할당 (Unallocated) CPU 익명 페이지 (Anonymous Page in RAM) Device Private (GPU VRAM) 스왑됨 (Swapped) (디스크/zswap) GUP 핀됨 (CPU RAM, 이동 불가) 해제됨 (Freed) COW 복사본 malloc + 첫 접근 (handle_mm_fault) GPU 폴트 / migrate_vma (mmap_read_lock) CPU 폴트 / migrate_to_ram swap out (shrink_folio_list) swap in (do_swap_page) GUP pin unpin_user_page GUP (migrate_to_ram → pin in RAM) munmap / exit OOM kill / exit fork + COW 잠금(Lock) 요약: • CPU↔GPU 마이그레이션: mmap_read_lock + page lock • swap in/out: mmap_read_lock + swap_lock • GUP pin: page lock + refcount

위 상태 전이도에서 핵심 전이는 다음과 같습니다:

전이 트리거 필요한 잠금 mmu_notifier
미할당 → CPU 익명 첫 접근 (demand fault) mmap_read_lock 호출 안 됨
CPU 익명 → Device Private GPU 폴트 / migrate_vma_setup mmap_read_lock + page lock invalidate_range
Device Private → CPU 익명 CPU 접근 / migrate_to_ram mmap_read_lock + page lock 호출 안 됨 (CPU 측)
CPU 익명 → 스왑됨 메모리 압박 (reclaim) page lock + swap_lock invalidate_range (GPU 매핑 시)
스왑됨 → CPU 익명 CPU 접근 (swap in) mmap_read_lock 호출 안 됨
CPU 익명 → GUP 핀됨 O_DIRECT, RDMA 등 page lock + refcount 해당 없음
Device Private → GUP 핀됨 GUP slow (migrate_to_ram 후 핀) mmap_read_lock + page lock migrate_to_ram 중 GPU 통지
any → 해제됨 munmap / exit / OOM mmap_write_lock release (exit 시)
CPU/Device → COW 복사본 fork + 접근 mmap_read_lock + page lock dup_mmap (fork 시)

실전 지연 시간 참고 데이터

HMM 기반 시스템의 성능을 이해하려면 각 인터커넥트(Interconnect)의 지연 시간과 대역폭, 그리고 HMM API 자체의 소프트웨어 오버헤드를 파악해야 합니다. 아래 데이터는 실제 시스템 측정과 하드웨어 사양서를 기반으로 한 참고 수치입니다.

항목 4KB 페이지 2MB 대형 페이지 비고
PCIe 4.0 x16 DMA ~2-5 μs ~60-80 μs 단방향 대역폭 ~25 GB/s
PCIe 5.0 x16 DMA ~1.5-4 μs ~35-50 μs 단방향 대역폭 ~50 GB/s
PCIe 6.0 x16 DMA ~1-3 μs ~20-35 μs 단방향 대역폭 ~121 GB/s (PAM4)
NVLink 3.0 (A100) ~0.5-1.5 μs ~10-20 μs 양방향 대역폭 ~600 GB/s
NVLink 4.0 (H100) ~0.3-1 μs ~7-15 μs 양방향 대역폭 ~900 GB/s
CXL 2.0 ~150-250 ns ~2-5 μs 캐시 코히어런트, ~32 GB/s
CXL 3.0 ~80-150 ns ~1-3 μs 캐시 코히어런트, ~64 GB/s
HMM 소프트웨어 오버헤드 일반적인 지연 시간 설명
hmm_range_fault (캐시 히트) ~1-5 μs 마이그레이션 불필요, 페이지 테이블 워크만
hmm_range_fault (폴트 필요) ~10-50 μs handle_mm_fault 포함, 페이지 할당
migrate_vma_setup + finalize (4KB) ~5-20 μs + DMA 시간 소프트웨어 설정 + 실제 DMA 전송
migrate_vma_setup + finalize (2MB) ~10-30 μs + DMA 시간 THP 마이그레이션, 512회 PTE 업데이트
mmu_notifier invalidate ~0.5-3 μs 콜백 체인 순회 + 드라이버 처리
GPU TLB flush ~1-10 μs GPU 아키텍처 의존, CU/SM 수에 비례
mmap_read_lock 경합 시 ~10-1000 μs 스레드 수에 따라 급증, per-VMA lock으로 완화
인터커넥트 단방향 대역폭 비교 (GB/s) 대역폭 (GB/s) 0 100 200 300 400 500 25 PCIe 4.0 50 PCIe 5.0 121 PCIe 6.0 32 CXL 2.0 64 CXL 3.0 300 NVLink 3 450 NVLink 4 HBM3 ~819 GB/s (참조선) PCIe CXL (캐시 코히어런트) NVLink (NVIDIA 전용)
참고 수치 안내: 위 지연 시간과 대역폭 데이터는 이상적인 조건에서의 근사치입니다. 실제 성능은 시스템 구성(CPU 모델, BIOS 설정, IOMMU 활성화 여부, NUMA 토폴로지), 워크로드 패턴(연속 vs 랜덤 접근, 페이지 크기, 동시 마이그레이션 수), 드라이버 구현(배치 크기, GPU 펜스 대기 방식)에 따라 크게 달라집니다. 벤치마크 시 perf stat과 GPU 프로파일러(rocprof, nsys)를 병행하여 측정하세요.
최적화 가이드라인:
  • 대형 페이지 사용: 4KB 페이지 512개보다 2MB 대형 페이지 1개를 마이그레이션하는 것이 DMA 설정 오버헤드를 1/512로 줄입니다.
  • 배치 마이그레이션: migrate_vma_setup()에서 가능한 한 넓은 범위를 한 번에 처리하세요.
  • 선제적 마이그레이션: GPU 커널 실행 전에 필요한 데이터를 미리 마이그레이션하여 GPU 폴트 지연을 제거하세요.
  • CXL 활용: CXL Type2를 사용하면 마이그레이션 없이 CPU/GPU 모두 접근 가능하여, 접근 패턴이 불규칙한 워크로드에 유리합니다.

참고자료

커널 문서

LWN 기사

커널 소스

다음 학습 경로:
  • GPU 서브시스템 (DRM/KMS) — HMM을 활용하는 GPU 드라이버 전체 구조
  • NPU (Neural Processing Unit) — AI 가속기와 DMA-BUF/HMM 통합
  • CXL 메모리 — ZONE_DEVICE Coherent를 사용하는 CXL Type2/3
  • 고급 메모리 관리 — 페이지 마이그레이션, NUMA 티어링 기반
  • NUMA — NUMA 노드와 메모리 티어 정책
  • DMA — DMA-BUF 버퍼 공유 메커니즘
  • MMU & TLB — HMM이 동기화하는 CPU 페이지 테이블 구조
  • Huge Pages — HMM 대형 페이지 마이그레이션의 기반
  • VMA / mmap — HMM이 조회하는 가상 메모리 영역 구조
  • IOMMU — GPU/NPU의 DMA 주소 변환(Address Translation) 및 보안 격리(Isolation)
  • ROCm/HIP — AMD GPU XNACK, SVM, KFD 드라이버
  • Spinlock — mmu_notifier 경로의 잠금 메커니즘