HMM (Heterogeneous Memory Management)
Linux 커널 HMM: CPU와 GPU가 동일한 가상 주소 공간을 공유하는 이기종 메모리 관리 프레임워크. ZONE_DEVICE, migrate_vma, SVM, DMA-BUF 통합, AI/ML 워크로드 최적화 종합 가이드.
핵심 요약
- 공유 가상 주소 — CPU와 GPU가 동일한 포인터로 메모리 접근 (제로 카피)
- ZONE_DEVICE — GPU VRAM을 Linux 페이지 프레임 번호(PFN)로 등록하는 특수 메모리 존
- migrate_vma — CPU 페이지를 GPU 메모리로 투명하게 이동시키는 API
- HMM fault handler — GPU가 없는 페이지에 접근 시 CPU→GPU 마이그레이션 자동 처리
- SVM (Shared Virtual Memory) — ROCm/CUDA Unified Memory의 커널 구현 기반
- DMA-BUF + HMM — NPU/카메라 등 다른 가속기와 제로 카피 버퍼 공유
- NUMA 티어링 — HMM 페이지를 NUMA 계층에 통합하여 자동 promotion/demotion
- hmm_range_fault() — 드라이버가 CPU 페이지 테이블을 읽어 GPU 페이지 테이블 동기화
단계별 이해
- 가상 주소 공간 공유 이해
CPU 프로세스의 VMA(가상 메모리 영역)를 GPU도 같이 사용하는 개념을 파악합니다. - ZONE_DEVICE 등록 방법 학습
GPU 드라이버가 VRAM을 Linux 메모리 서브시스템에 등록하는 과정을 이해합니다. - migrate_vma API 실습
페이지를 CPU↔GPU 간 이동시키는 API 사용법을 익힙니다. - HMM fault handler 구현
GPU가 없는 페이지에 접근했을 때 자동 마이그레이션이 일어나는 흐름을 추적합니다. - SVM과 ROCm 연동 확인
AMD ROCm의 KFD 드라이버가 HMM을 어떻게 활용하는지 소스에서 확인합니다. - 진단 도구 활용
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 전용 포인터) | 동일 포인터 사용 |
| 오버헤드 | 복사 대역폭 비용 | 페이지 폴트 비용 |
| API | cudaMemcpy, clEnqueue | 일반 malloc/mmap |
| 구현 | 드라이버 독자적 | 커널 mm/hmm.c 공통 |
HMM 아키텍처
HMM의 핵심은 CPU 페이지 테이블과 GPU 페이지 테이블을 동기화하는 메커니즘입니다.
mmu_notifier를 통해 CPU 페이지 테이블 변경(매핑 해제, 페이지 이동 등)을 GPU 드라이버에 통지합니다.
핵심 구조체
#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);
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_PRIVATE | GPU VRAM (CPU 직접 접근 불가) | migrate_vma로만 접근 |
MEMORY_DEVICE_COHERENT | CXL Type2, GPU UMAP 영역 | CPU load/store 가능 |
MEMORY_DEVICE_FS_DAX | DAX 파일시스템 (PMEM) | DAX mmap |
MEMORY_DEVICE_GENERIC | 일반 장치 메모리 | DMA 전용 |
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;
}
cudaMallocManaged()로 할당하고, 페이지 폴트 시 자동 마이그레이션이 이루어집니다.
AMD ROCm은 동일한 패턴을 Linux HMM 표준 API 위에 구현하여 커널 업스트림과 더 긴밀하게 통합됩니다.
DMA-BUF와 HMM 결합
DMA-BUF는 여러 디바이스 드라이버가 동일한 메모리 버퍼를 공유하는 메커니즘입니다. HMM과 결합하면 GPU 메모리, NPU 메모리, 카메라 버퍼가 모두 같은 물리 메모리를 참조할 수 있어 제로 카피 AI 파이프라인을 구현할 수 있습니다.
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 + migrate | PCIe 마이그레이션 |
migrate_vma 마이그레이션 흐름
페이지를 CPU 메모리에서 GPU VRAM으로 이동시키는 전체 흐름을 설명합니다.
migrate_vma_setup() → migrate_vma_pages() → migrate_vma_finalize() 3단계로 구성됩니다.
/* 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합니다.
# 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
드라이버 API — hmm_range_fault()
hmm_range_fault()는 GPU 드라이버가 CPU 페이지 테이블의 현재 상태를 조회하여
GPU 페이지 테이블을 동기화할 때 사용하는 핵심 API입니다.
CPU 페이지가 스왑 아웃되어 있으면 자동으로 스왑 인도 수행합니다.
/* 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);
진단 및 디버깅
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
커널 소스 가이드
| 파일 / 디렉토리 | 설명 |
|---|---|
mm/hmm.c | HMM 핵심 구현 — hmm_range_fault(), mmu_notifier 통합 |
mm/migrate_device.c | migrate_vma_setup/pages/finalize() 구현 |
mm/memremap.c | memremap_pages(), ZONE_DEVICE 등록 |
include/linux/hmm.h | HMM 공개 API — hmm_range, HMM_PFN_* 플래그 |
include/linux/memremap.h | dev_pagemap, dev_pagemap_ops 구조체 |
include/linux/mmu_notifier.h | mmu_notifier_ops, mmu_interval_notifier |
drivers/gpu/drm/amd/amdkfd/kfd_svm.c | AMD ROCm SVM 구현 (HMM 활용 참고) |
drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | KFD GPU↔CPU 마이그레이션 로직 |
drivers/gpu/drm/nouveau/ | Nouveau HMM 구현 (NVIDIA 오픈소스) |
lib/test_hmm.c | HMM 테스트 모듈 |
커널 설정
# 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
HMM 통합 체크리스트
HMM 도입의 핵심 리스크는 CPU/GPU 페이지 테이블 불일치와 마이그레이션 경쟁 조건입니다. 드라이버 통합 시 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
관련 문서
- 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 버퍼 공유 메커니즘