NPU (Neural Processing Unit)
Linux NPU 드라이버를 AI 추론 워크로드의 지연(Latency)시간·전력·메모리 효율 관점에서 심층 정리합니다. DRM accel 계층과 커맨드 제출 모델, 사용자 공간(User Space) 런타임과 커널 ioctl 경계, 텐서 버퍼(Buffer) 할당과 DMA-BUF 공유, IOMMU/PASID 기반 주소 공간(Address Space) 격리(Isolation), 펌웨어(Firmware) 로딩과 에러 복구 시나리오, DVFS/thermal/runtime PM 연계 전력 최적화, CPU/GPU/NPU 협업 파이프라인(Pipeline) 구성과 프로파일링(Profiling)까지 실전 NPU 스택 구축에 필요한 핵심 내용을 다룹니다.
핵심 요약
- NPU Accelerator — 딥러닝 추론/학습에 특화된 하드웨어 가속기로, 행렬 곱셈(GEMM)과 저정밀도 연산(INT8/FP16)에 최적화된 고정 함수 파이프라인입니다.
- DRM Accel 서브시스템 — 메인라인 커널의
drivers/accel/프레임워크로, NPU를 GPU와 별도의 디바이스 노드(/dev/accel/accel*)로 분리 관리합니다. - DMA Model for Tensor Operations — NPU와 호스트 메모리 간 텐서 데이터를 DMA로 전송하며, scatter-gather 리스트와 IOMMU 매핑을 활용하여 대용량 버퍼를 효율적으로 이동합니다.
- Model Compilation — ONNX/TFLite 등 고수준 모델을 NPU 명령어로 변환하는 오프라인 컴파일 단계로, 연산 그래프 최적화와 메모리 레이아웃 결정이 수행됩니다.
- On-device Inference — 컴파일된 모델을 NPU 펌웨어에 로드하고, 입력 텐서 전송 → 추론 실행 → 출력 텐서 수신의 파이프라인으로 동작합니다.
- Kernel Driver Framework — NPU 드라이버는 장치 초기화, 펌웨어 로드, 작업 제출/완료 큐, 전원 관리(Runtime PM)를 담당하며,
ioctl기반으로 유저 공간과 통신합니다. - Memory Management for Accelerators — NPU 전용 메모리(SRAM/HBM)와 시스템 메모리 간 버퍼 공유를 위해
dma-buf,dma-heap, CMA 등의 메커니즘을 사용합니다. - Performance Profiling — NPU 하드웨어 카운터, 타임스탬프, 대역폭 측정을 통해 추론 지연(Latency)과 처리량(Throughput)을 분석하고 모델/드라이버를 최적화합니다.
단계별 이해
- NPU 하드웨어 아키텍처 파악 — MAC(Multiply-Accumulate) 어레이, 온칩 SRAM, DMA 엔진 등 NPU 내부 구성요소의 역할과 데이터 흐름을 이해합니다.
GPU가 범용 SIMT(Single Instruction Multiple Thread) 구조인 반면, NPU는 특정 연산 패턴에 최적화된 데이터플로 아키텍처를 사용합니다.
- DRM Accel 드라이버 구조 분석 —
drm_accel_register()로 등록하고, GEM(Graphics Execution Manager) 객체로 메모리를 관리하며,ioctl로 작업을 제출하는 구조를 파악합니다./dev/accel/accel0을 통해 유저 공간 런타임이 모델 로드, 추론 요청, 결과 수신을 수행합니다. - 모델 컴파일과 펌웨어 로드 흐름 추적 — 고수준 모델을 NPU 바이너리로 컴파일하고, 드라이버가
request_firmware()로 펌웨어를 로드한 뒤 작업 큐에 추론 명령을 제출하는 과정을 따라갑니다.컴파일 단계에서 연산 그래프 분할, 메모리 할당 계획, 명령어 스케줄링이 결정되므로 런타임 성능에 직접적 영향을 미칩니다.
- DMA와 메모리 관리 점검 — 호스트-NPU 간 텐서 데이터 전송에
dma-buf공유, CMA 할당, IOMMU 매핑이 올바르게 설정되었는지 확인합니다.제로 카피(Zero-copy) 경로가 가능한 경우
dma-bufimport/export로 카메라→NPU→디스플레이 파이프라인을 구성합니다. - 성능 프로파일링과 전력 관리 — NPU 하드웨어 카운터와 타임스탬프로 추론 지연을 측정하고, Runtime PM으로 유휴 시 클록/전원을 절감합니다.
배치 크기, 양자화(Quantization) 수준, 클록 주파수를 조합하여 지연-처리량-전력 삼각 균형을 최적화합니다.
drivers/accel/)은 NPU와 GPU를 별도의 디바이스 노드(/dev/accel/accel* vs /dev/dri/renderD*)로 분리해 관리하며, 실제 지원 범위는 커널 브랜치/배포판 백포트에 따라 달라질 수 있습니다.
NPU (Neural Processing Unit) 개요
NPU(Neural Processing Unit)는 딥러닝 추론/학습 워크로드에 최적화된 전용 하드웨어 가속기입니다.
메인라인의 DRM Accel(drivers/accel/) 서브시스템은 GPU와 분리된 전용 디바이스 노드(/dev/accel/accel0)를 통해 NPU 및 AI 가속기를 관리하며, 커널 버전별 구체 기능은 릴리스/배포판 정책을 확인해야 합니다.
/dev/dri/renderD*)와 별도의 /dev/accel/accel* 디바이스 노드를 사용합니다.
이를 통해 GPU용 유저 공간 도구(Mesa, Vulkan)와 NPU용 ML 프레임워크가 충돌 없이 공존할 수 있습니다.
NPU 설계 접근 방식: ASIC vs FPGA
NPU 하드웨어는 크게 두 가지 설계 방식으로 나뉩니다.
| 특성 | ASIC 기반 NPU | FPGA 기반 NPU |
|---|---|---|
| 설계 유연성 | 고정 — 제조 후 변경 불가 | 재구성 가능(Reconfigurable) — 비트스트림 교체 |
| 성능 | 높음 — 전용 실리콘 최적화 | 중간 — 라우팅 오버헤드 |
| 전력 효율 | 최고 (TOPS/W) | ASIC 대비 3~10배 높은 소비전력 |
| 개발 비용 | 수억~수십억 원 (마스크 비용) | 낮음 — HDL 수정만으로 반복 |
| 시장 출시 | 12~24개월 | 수 주~수 개월 |
| 대표 사례 | Google TPU, Apple ANE, Intel NPU | AMD XDNA (Versal 기반), Xilinx DPU |
| 리눅스 드라이버 | 전용 드라이버 (ivpu, habanalabs) | FPGA 관리자 + 오버레이 (fpga_mgr + accel) |
데이터플로우 아키텍처(Dataflow Architecture)
NPU의 핵심 설계 원리는 데이터플로우 아키텍처입니다. CPU의 폰 노이만(Von Neumann) 모델이 명령어 스트림을 중심으로 실행 순서를 결정하는 반면, 데이터플로우 아키텍처는 데이터의 가용성(Data Availability)에 따라 연산을 트리거합니다.
데이터플로우 아키텍처의 핵심 요소는 다음과 같습니다:
- 정적 스케줄링: 컴파일 시점에 모든 연산 순서와 데이터 이동 경로가 결정됨. 런타임 분기 예측이 불필요
- 공간적 연산(Spatial Computing): 연산 유닛이 물리적으로 배열되어 데이터가 유닛 사이를 흐르며 처리됨
- 파이프라인 병렬성: 여러 레이어의 연산이 동시에 다른 타일에서 실행되어 처리량 극대화
- 로컬 메모리 우선: 글로벌 메모리 접근을 최소화하고 온칩 SRAM(Scratchpad)에서 데이터 재사용
시스톨릭 어레이(Systolic Array) 상세
시스톨릭 어레이는 NPU의 핵심 연산 구조로, 처리 요소(Processing Element, PE)가 2D 그리드로 배열됩니다. 각 PE는 곱셈-누적(Multiply-Accumulate, MAC) 연산을 수행하며, 데이터는 심장 박동(Systole)처럼 한 클럭마다 인접 PE로 전달됩니다.
/* 시스톨릭 어레이 동작 원리 (4×4 예시)
*
* 가중치(W)는 사전 로드, 입력(A)은 왼쪽에서 우측으로 이동
* 부분합(Partial Sum)은 위에서 아래로 누적
*
* 클럭 0: 클럭 1: 클럭 2:
* ┌────┬────┬────┬────┐ ┌────┬────┬────┬────┐ ┌────┬────┬────┬────┐
* │a00 │ │ │ │ │a01 │a00 │ │ │ │a02 │a01 │a00 │ │
* │×w00│ │ │ │ │×w00│×w01│ │ │ │×w00│×w01│×w02│ │
* ├────┼────┼────┼────┤ ├────┼────┼────┼────┤ ├────┼────┼────┼────┤
* │ │ │ │ │ │a10 │ │ │ │ │a11 │a10 │ │ │
* │ │ │ │ │ │×w10│ │ │ │ │×w10│×w11│ │ │
* ├────┼────┼────┼────┤ ├────┼────┼────┼────┤ ├────┼────┼────┼────┤
* │ │ │ │ │ │ │ │ │ │ │a20 │ │ │ │
* │ │ │ │ │ │ │ │ │ │ │×w20│ │ │ │
* └────┴────┴────┴────┘ └────┴────┴────┴────┘ └────┴────┴────┴────┘
*
* 결과 C[i][j] = Σ A[i][k] × W[k][j]
* N×N 행렬: 2N-1 클럭에 N² MAC 연산 완료
* → O(N) 시간 복잡도 (순차 O(N³) 대비)
*/
| NPU | 어레이 크기 | 데이터 타입 | 클럭당 MAC | 피크 TOPS (INT8) |
|---|---|---|---|---|
| Google TPU v4 | 128×128 × 4 코어 | BF16/INT8 | 65,536 | ~275 |
| Apple ANE (M3) | 16코어 × 내부 배열 | FP16/INT8 | 비공개 | ~18 |
| Intel NPU (Lunar Lake) | 2 NCE × 내부 배열 | INT8/FP16 | 비공개 | ~48 |
| Qualcomm HTP v75 | 벡터+텐서 슬라이스 | INT4/INT8/FP16 | 비공개 | ~75 |
| Huawei DaVinci | 16×16×16 3D 큐브 | FP16/INT8 | 4,096 (FP16) | ~512 |
NPU vs GPU vs CPU 연산 특성
| 특성 | CPU | GPU | NPU |
|---|---|---|---|
| 아키텍처 | 범용 파이프라인, 소수의 고성능 코어 | 대량의 SIMD/SIMT 코어 | MAC 어레이 + 전용 데이터 흐름 엔진 |
| 연산 강점 | 분기 예측(Branch Prediction), 직렬 연산 | 대규모 병렬 부동소수점 | INT8/FP16 행렬 곱셈 (GEMM) |
| 전력 효율 | 낮음 (범용) | 중간 (SIMD 활용) | 높음 (고정 함수 + 저정밀도) |
| 메모리 접근 | 캐시(Cache) 계층 (L1/L2/L3) | HBM/GDDR + 텍스처 캐시 | SRAM 스크래치패드 + DMA |
| 프로그래밍 모델 | 일반 코드 | CUDA/OpenCL/Vulkan Compute | 컴파일러가 생성한 명령 스트림 |
| 대표 TOPS/W | ~0.1 | ~1–5 | ~10–50+ |
NPU 하드웨어 아키텍처 개념
NPU의 핵심은 Systolic Array(맥동 배열)를 기반으로 한 행렬 연산 유닛과 전용 데이터 흐름 제어(Flow Control)기입니다. 대부분의 NPU는 다음과 같은 공통 구조를 가집니다.
NPU 데이터 흐름
NPU에서 추론을 수행하는 전체 데이터 흐름은 다음과 같습니다.
- 모델 컴파일: ONNX/TFLite 모델을 NPU 전용 바이너리(blob)로 오프라인 컴파일. 레이어별 타일링, 양자화, 메모리 할당 계획이 포함됨
- 버퍼 할당: GEM BO(Buffer Object)를 통해 입력/출력/가중치 버퍼를 커널 드라이버에 할당 요청
- 커맨드 버퍼 생성: UMD가 DMA 전송, 연산 커맨드 등을 커맨드 버퍼에 기록
- 잡 제출: ioctl을 통해 커맨드 버퍼를 커널에 제출 →
drm_sched가 스케줄링 - HW 실행: 펌웨어가 레이어 단위로 Compute Cluster에 디스패치(Dispatch), SRAM ↔ DDR DMA 전송 관리
- 완료 통보: IRQ →
dma_fence시그널(Signal) → 유저 공간에 결과 전달
추론 파이프라인(Inference Pipeline) 상세
실제 NPU 추론은 단일 레이어 실행이 아닌, 여러 레이어가 파이프라인 형태로 연속 실행됩니다. 컴파일러가 생성한 커맨드 버퍼에는 다음과 같은 명령들이 인코딩됩니다.
/* NPU 커맨드 버퍼 내부 구조 (개념적 예시)
*
* 각 레이어의 실행은 다음 단계로 구성:
* ┌─────────────────────────────────────────────────┐
* │ CMD_DMA_LOAD src=DDR+0x1000 dst=SRAM_A sz=64K │ ← 가중치 사전 로드
* │ CMD_DMA_LOAD src=DDR+0x8000 dst=SRAM_B sz=16K │ ← 입력 텐서 로드
* │ CMD_BARRIER wait=DMA_DONE │ ← DMA 완료 대기
* │ CMD_COMPUTE op=CONV2D tile=0 cfg=0x42 │ ← MAC 어레이 연산
* │ CMD_COMPUTE op=RELU tile=0 cfg=0x01 │ ← 활성화 함수
* │ CMD_BARRIER wait=COMPUTE_DONE │ ← 연산 완료 대기
* │ CMD_DMA_STORE src=SRAM_C dst=DDR+0xA000 sz=16K │ ← 결과 저장
* │ ... (다음 레이어 반복) ... │
* │ CMD_SIGNAL_FENCE fence_addr=0xF000 │ ← 완료 시그널
* └─────────────────────────────────────────────────┘
*
* 더블 버퍼링: 레이어 N 연산 중 레이어 N+1의 DMA 로드가 동시 실행
* → DMA와 연산이 겹쳐 파이프라인 효율 극대화
*/
양자화(Quantization)와 데이터 타입
NPU는 전력 효율을 위해 낮은 정밀도의 데이터 타입을 사용합니다. MAC 어레이의 데이터 타입에 따라 연산 처리량(TOPS)이 크게 달라집니다.
| 데이터 타입 | 비트 폭 | 범위/정밀도 | 용도 | 상대 처리량 |
|---|---|---|---|---|
| FP32 | 32-bit | IEEE 754 단정밀도 | 학습 기준선 | 1× (기준) |
| FP16 | 16-bit | ±65504, 정밀도 ~3.3자리 | 학습/추론 혼합 | 2× |
| BF16 | 16-bit | FP32 동적 범위, 낮은 정밀도 | 학습 (기울기 안정성) | 2× |
| INT8 | 8-bit | -128~127 / 0~255 | 추론 최적화 (PTQ/QAT) | 4× |
| INT4 | 4-bit | -8~7 / 0~15 | LLM 가중치 압축 | 8× |
| FP8 (E4M3/E5M2) | 8-bit | E4M3: ±448 / E5M2: ±57344 | 차세대 학습/추론 | 4× |
- PTQ (Post-Training Quantization): 학습 완료 후 가중치/활성화를 INT8로 변환. 캘리브레이션 데이터셋으로 스케일 팩터 결정
- QAT (Quantization-Aware Training): 학습 중에 양자화 시뮬레이션. PTQ보다 정확도 손실이 적음
- Mixed Precision: 레이어별로 FP16/INT8을 혼합. 정확도에 민감한 레이어는 FP16, 나머지는 INT8 사용
DRM Accel 서브시스템
DRM Accel은 Linux 6.2에서 도입된 NPU/AI 가속기 전용 프레임워크입니다. DRM의 인프라를 재사용하면서도 GPU와 별개의 디바이스 노드 네임스페이스(Namespace)를 제공합니다.
디바이스 노드 구조
# GPU 디바이스 노드 (DRM)
/dev/dri/card0 # DRM master (KMS + 렌더)
/dev/dri/renderD128 # render-only (non-root GPU 접근)
# NPU/Accel 디바이스 노드 (DRM Accel)
/dev/accel/accel0 # AI 가속기 전용 노드
/dev/accel/accel1 # 두 번째 가속기 (있는 경우)
# sysfs 계층
/sys/class/accel/accel0/
├── device # → PCI/platform 디바이스 심링크
├── dev # major:minor 번호
└── power/ # 전원 관리 속성
Accel 드라이버 등록(Driver Registration)
Accel 드라이버는 drm_dev_alloc() 대신 accel_minor_alloc()을 내부적으로 사용합니다.
드라이버는 drm_driver에 DRIVER_COMPUTE_ACCEL 플래그를 설정하여 Accel 모드를 활성화합니다.
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
/* Accel 드라이버 플래그: DRIVER_COMPUTE_ACCEL을 설정하면
* /dev/accel/accelN 노드가 생성됨 (GPU용 /dev/dri/* 대신)
* 참고: DRIVER_RENDER와 동시에 사용 불가 */
static const struct drm_driver my_npu_driver = {
.driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL,
.name = "my_npu",
.desc = "My NPU Accelerator",
.date = "20250101",
.major = 1,
.minor = 0,
/* GEM 오퍼레이션 */
.gem_create_object = my_npu_gem_create_object,
/* ioctl 테이블 */
.ioctls = my_npu_ioctls,
.num_ioctls = ARRAY_SIZE(my_npu_ioctls),
/* 파일 오퍼레이션 */
.fops = &my_npu_fops,
};
/* probe에서 등록 */
static int my_npu_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct drm_device *drm;
struct my_npu_device *ndev;
int ret;
ndev = devm_drm_dev_alloc(&pdev->dev, &my_npu_driver,
struct my_npu_device, drm);
if (IS_ERR(ndev))
return PTR_ERR(ndev);
drm = &ndev->drm;
pci_set_drvdata(pdev, ndev);
/* HW 초기화: MMIO 매핑, IRQ 등록, 펌웨어 로딩 */
ret = my_npu_hw_init(ndev);
if (ret)
return ret;
/* drm_dev_register()가 내부적으로 accel_minor를 할당
* → /dev/accel/accelN 생성 */
ret = drm_dev_register(drm, 0);
if (ret)
goto err_hw;
dev_info(&pdev->dev, "NPU registered as /dev/accel/accel%d\\n",
drm->accel->index);
return 0;
err_hw:
my_npu_hw_fini(ndev);
return ret;
}
GEM Buffer Object 관리
NPU 드라이버는 GEM을 사용해 가중치, 텐서, 커맨드 버퍼 등의 메모리를 관리합니다.
일반적으로 drm_gem_shmem_object 헬퍼를 확장하거나, 독자적인 GEM 오브젝트를 구현합니다.
/* NPU 전용 GEM 버퍼 오브젝트 */
struct my_npu_bo {
struct drm_gem_shmem_object base;
u64 npu_addr; /* NPU 가상 주소 (디바이스 MMU 매핑) */
enum {
NPU_BO_INPUT = 0, /* 입력 텐서 */
NPU_BO_OUTPUT = 1, /* 출력 텐서 */
NPU_BO_WEIGHTS = 2, /* 모델 가중치 (읽기 전용) */
NPU_BO_CMDQ = 3, /* 커맨드 큐 */
} usage;
bool shared; /* DMA-BUF로 다른 디바이스와 공유 가능 */
};
/* GEM 생성 ioctl 핸들러 */
static int my_npu_gem_create_ioctl(struct drm_device *dev,
void *data,
struct drm_file *file)
{
struct my_npu_create_bo *args = data;
struct my_npu_bo *bo;
int ret;
args->size = ALIGN(args->size, SZ_4K);
bo = my_npu_bo_alloc(dev, args->size, args->flags);
if (IS_ERR(bo))
return PTR_ERR(bo);
/* NPU MMU에 매핑 → npu_addr 할당 */
ret = my_npu_mmu_map(bo);
if (ret)
goto err_free;
ret = drm_gem_handle_create(file, &bo->base.base, &args->handle);
drm_gem_object_put(&bo->base.base);
args->npu_addr = bo->npu_addr;
return ret;
err_free:
drm_gem_object_put(&bo->base.base);
return ret;
}
컴퓨트 잡 제출 (Job Submission)
NPU 드라이버는 drm_sched(GPU 스케줄러(Scheduler))를 재사용하여 컴퓨트 잡을 관리합니다.
/* NPU 잡 구조체 — drm_sched_job을 임베드 */
struct my_npu_job {
struct drm_sched_job base;
struct my_npu_bo *cmd_bo; /* 커맨드 버퍼 */
struct my_npu_bo **bos; /* 입출력 버퍼 참조 */
u32 bo_count;
struct dma_fence *done_fence; /* 완료 fence */
};
/* drm_sched 백엔드 콜백 */
static struct dma_fence *
my_npu_run_job(struct drm_sched_job *sched_job)
{
struct my_npu_job *job =
container_of(sched_job, struct my_npu_job, base);
struct my_npu_device *ndev = job_to_ndev(job);
/* 1. 커맨드 버퍼의 NPU 가상 주소를 HW 레지스터에 기록 */
writel(lower_32_bits(job->cmd_bo->npu_addr),
ndev->mmio + NPU_REG_CMD_ADDR_LO);
writel(upper_32_bits(job->cmd_bo->npu_addr),
ndev->mmio + NPU_REG_CMD_ADDR_HI);
/* 2. 실행 트리거 — 펌웨어가 처리 시작 */
writel(NPU_CMD_EXECUTE, ndev->mmio + NPU_REG_DOORBELL);
/* 3. HW fence 반환 — IRQ 핸들러에서 시그널됨 */
return dma_fence_get(job->done_fence);
}
static enum drm_gpu_sched_stat
my_npu_timedout_job(struct drm_sched_job *sched_job)
{
struct my_npu_job *job =
container_of(sched_job, struct my_npu_job, base);
dev_err(job->dev, "NPU job timeout — resetting engine\\n");
my_npu_engine_reset(job_to_ndev(job));
return DRM_GPU_SCHED_STAT_NOMINAL;
}
static const struct drm_sched_backend_ops my_npu_sched_ops = {
.run_job = my_npu_run_job,
.timedout_job = my_npu_timedout_job,
};
펌웨어 로딩
대부분의 NPU는 부팅 시 펌웨어를 로딩해야 합니다. 커널의 request_firmware() API를 사용하여
/lib/firmware/에서 NPU 펌웨어 바이너리를 로드합니다.
static int my_npu_load_firmware(struct my_npu_device *ndev)
{
const struct firmware *fw;
int ret;
ret = request_firmware(&fw, "my_npu/fw.bin", ndev->dev);
if (ret) {
dev_err(ndev->dev, "firmware load failed: %d\\n", ret);
return ret;
}
/* 펌웨어를 NPU의 내부 SRAM 또는 전용 메모리 영역에 복사 */
memcpy_toio(ndev->fw_region, fw->data, fw->size);
/* 부트 레지스터 — 펌웨어 실행 시작 */
writel(NPU_FW_BOOT, ndev->mmio + NPU_REG_FW_CTRL);
/* 부팅 완료 대기 (mailbox 통신) */
ret = my_npu_wait_fw_ready(ndev, 5000);
release_firmware(fw);
return ret;
}
인터럽트 처리
NPU 드라이버의 인터럽트 핸들러는 잡 완료 통보, 에러 보고, 펌웨어 메시지 수신 등을 처리합니다. 대부분의 NPU는 MSI/MSI-X 인터럽트를 사용하며, 여러 이벤트 타입을 하나의 IRQ 라인으로 멀티플렉싱합니다.
/* NPU 인터럽트 핸들러 */
static irqreturn_t my_npu_irq_handler(int irq, void *data)
{
struct my_npu_device *ndev = data;
u32 status = readl(ndev->mmio + NPU_REG_IRQ_STATUS);
if (!status)
return IRQ_NONE;
/* 인터럽트 소스별 처리 */
if (status & NPU_IRQ_JOB_DONE) {
/* 잡 완료 → dma_fence 시그널 */
u32 fence_id = readl(ndev->mmio + NPU_REG_DONE_FENCE);
struct dma_fence *fence = my_npu_find_fence(ndev, fence_id);
if (fence) {
dma_fence_signal(fence);
dma_fence_put(fence);
}
}
if (status & NPU_IRQ_FW_MSG) {
/* 펌웨어 메시지 → 워크큐에서 처리 */
queue_work(ndev->wq, &ndev->fw_msg_work);
}
if (status & NPU_IRQ_ERROR) {
/* HW 에러 → 에러 복구 시작 */
u32 err_code = readl(ndev->mmio + NPU_REG_ERROR_CODE);
dev_err(ndev->dev, "NPU error: 0x%08x\\n", err_code);
queue_work(ndev->wq, &ndev->error_work);
}
/* 인터럽트 ACK */
writel(status, ndev->mmio + NPU_REG_IRQ_CLEAR);
return IRQ_HANDLED;
}
/* probe에서 IRQ 등록 */
static int my_npu_irq_init(struct my_npu_device *ndev,
struct pci_dev *pdev)
{
int ret;
/* MSI-X 벡터 할당 */
ret = pci_alloc_irq_vectors(pdev, 1, 4,
PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (ret < 0)
return ret;
/* IRQ 핸들러 등록 */
ret = request_irq(pci_irq_vector(pdev, 0),
my_npu_irq_handler,
0, "my_npu", ndev);
return ret;
}
펌웨어 에러 복구
NPU 펌웨어가 비정상 종료하면 전체 NPU를 리셋하고 펌웨어를 재로딩해야 합니다. 이 과정에서 실행 중이던 모든 잡의 fence를 에러로 시그널하고, 대기 중이던 잡들을 재스케줄링합니다.
/* 펌웨어 크래시 복구 시퀀스 */
static void my_npu_fw_recovery(struct work_struct *work)
{
struct my_npu_device *ndev =
container_of(work, struct my_npu_device, error_work);
dev_warn(ndev->dev, "Starting NPU firmware recovery\\n");
/* 1. 스케줄러 정지 — 새 잡 제출 차단 */
drm_sched_stop(&ndev->sched, NULL);
/* 2. 실행 중/대기 중 잡의 fence를 에러로 시그널 */
my_npu_fail_all_pending_jobs(ndev, -EIO);
/* 3. HW 리셋 (클럭 리셋 또는 PCIe FLR) */
my_npu_hw_reset(ndev);
/* 4. 펌웨어 재로딩 */
if (my_npu_load_firmware(ndev)) {
dev_err(ndev->dev, "FW reload failed — device offline\\n");
ndev->is_dead = true;
return;
}
/* 5. MMU 페이지 테이블 복원 */
my_npu_mmu_restore_all(ndev);
/* 6. 스케줄러 재시작 */
drm_sched_start(&ndev->sched);
dev_info(ndev->dev, "NPU firmware recovery complete\\n");
ndev->recovery_count++;
}
NPU MMU (디바이스 가상 메모리(Virtual Memory))
NPU는 자체 MMU(또는 IOMMU 연동)를 통해 디바이스 가상 주소 공간을 관리합니다. 이를 통해 연속적이지 않은 물리 페이지(Page)를 NPU에서 연속 주소로 접근할 수 있습니다.
struct my_npu_mmu {
struct my_npu_device *ndev;
dma_addr_t pgd_dma; /* L1 페이지 디렉토리 */
u64 *pgd_cpu; /* CPU 매핑 */
struct drm_mm va_space; /* VA 할당 관리 */
struct mutex lock;
};
static int my_npu_mmu_map(struct my_npu_bo *bo)
{
struct my_npu_mmu *mmu = &bo_to_ndev(bo)->mmu;
struct drm_mm_node *node = &bo->mm_node;
struct sg_table *sgt;
int ret;
mutex_lock(&mmu->lock);
/* VA 공간에서 영역 할당 */
ret = drm_mm_insert_node_generic(&mmu->va_space, node,
bo->base.base.size,
SZ_4K, 0, DRM_MM_INSERT_BEST);
if (ret)
goto out;
bo->npu_addr = node->start;
sgt = drm_gem_shmem_get_pages_sgt(&bo->base);
if (IS_ERR(sgt)) {
drm_mm_remove_node(node);
ret = PTR_ERR(sgt);
goto out;
}
my_npu_mmu_write_ptes(mmu, bo->npu_addr, sgt);
my_npu_mmu_flush_tlb(mmu);
out:
mutex_unlock(&mmu->lock);
return ret;
}
Accel 서브시스템 상세
DRM Accel 서브시스템은 Linux 6.2에서 Oded Gabbay에 의해 도입되었습니다. 기존 DRM 인프라를 재사용하면서도 GPU와 독립된 디바이스 네임스페이스를 제공하여, ML 프레임워크와 그래픽 스택이 충돌하지 않도록 설계되었습니다.
Accel vs misc device vs platform driver 접근 방식
NPU 커널 드라이버를 구현할 때 선택할 수 있는 세 가지 주요 접근 방식이 있습니다.
| 접근 방식 | 디바이스 노드 | 제공 인프라 | 적합한 경우 | 사례 |
|---|---|---|---|---|
| DRM Accel | /dev/accel/accel* |
GEM, DMA-BUF, drm_sched, drm_mm, dma_fence | 본격적인 NPU/AI 가속기, GPU와 유사한 복잡도 | ivpu, habanalabs, amdxdna, qaic |
| misc device | /dev/xyz |
최소한의 chardev 프레임워크 | 단순한 인터페이스, DRM 인프라 불필요 | fastrpc (Qualcomm DSP) |
| platform driver | sysfs 또는 커스텀 chardev | Device Tree/ACPI 바인딩, PM 통합 | SoC 내장 가속기, DTS 기반 설정 | 일부 임베디드(Embedded) NPU |
/* DRM Accel 접근 방식: DRIVER_COMPUTE_ACCEL 플래그 */
static const struct drm_driver accel_driver = {
.driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL,
/* DRIVER_RENDER와 DRIVER_COMPUTE_ACCEL은 상호 배타적
* DRIVER_RENDER → /dev/dri/renderDN
* DRIVER_COMPUTE_ACCEL → /dev/accel/accelN */
};
/* misc device 접근 방식: 단순 chardev */
static struct miscdevice npu_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "my_npu",
.fops = &npu_fops,
};
/* platform driver 접근 방식: Device Tree 바인딩 */
static const struct of_device_id npu_of_match[] = {
{ .compatible = "vendor,my-npu-v1" },
{ },
};
static struct platform_driver npu_platform_driver = {
.probe = npu_probe,
.remove = npu_remove,
.driver = {
.name = "my_npu",
.of_match_table = npu_of_match,
.pm = &npu_pm_ops,
},
};
Accel 문자 장치 내부 구조
Accel 디바이스 노드는 DRM의 drm_minor 인프라를 재사용하지만, 별도의 major 번호(261)를 사용합니다.
drm_accel.c가 /dev/accel/ 네임스페이스를 관리하며, GPU와 완전히 분리된 chardev 등록을 수행합니다.
/* drm_accel.c 핵심 등록 흐름 (커널 소스 기반 요약) */
/* 1. Accel 서브시스템 초기화 — DRM 코어 부팅 시 호출 */
void accel_core_init(void)
{
/* major=261로 문자 장치 영역 등록 */
register_chrdev_region(MKDEV(ACCEL_MAJOR, 0),
ACCEL_MAX_MINORS, "accel");
/* /sys/class/accel/ 생성 */
accel_class = class_create("accel");
}
/* 2. 드라이버가 DRIVER_COMPUTE_ACCEL 플래그를 설정하고
* drm_dev_register()를 호출하면 내부적으로 accel minor 할당 */
int accel_minor_alloc(struct drm_device *dev)
{
/* /dev/accel/accelN 문자 장치 생성
* drm_minor 구조체를 재사용하되 accel idr에서 인덱스 할당 */
struct drm_minor *minor;
minor = drm_minor_alloc(dev, DRM_MINOR_ACCEL);
/* device_create(accel_class, ...) → /dev/accel/accelN */
return drm_minor_register(dev, DRM_MINOR_ACCEL);
}
/* 3. open() 시 drm_file 구조체 할당
* → 프로세스별 GEM 핸들 테이블, VA 컨텍스트 생성 */
Accel ioctl 처리 흐름
사용자 공간에서 /dev/accel/accel0에 ioctl을 호출하면, DRM 코어의 ioctl 디스패처가
드라이버별 핸들러로 라우팅합니다. Accel과 GPU는 동일한 DRM ioctl 인프라를 공유합니다.
/* NPU ioctl 테이블 정의 예시 */
static const struct drm_ioctl_desc my_npu_ioctls[] = {
/* DRM_IOCTL_DEF_DRV(이름, 핸들러, 플래그) */
DRM_IOCTL_DEF_DRV(MY_NPU_BO_CREATE,
my_npu_bo_create_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MY_NPU_BO_INFO,
my_npu_bo_info_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MY_NPU_SUBMIT,
my_npu_submit_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MY_NPU_WAIT,
my_npu_wait_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MY_NPU_GET_PARAM,
my_npu_get_param_ioctl,
DRM_RENDER_ALLOW),
};
/* uapi 헤더 — 사용자 공간에 노출되는 ioctl 번호 */
#define DRM_MY_NPU_BO_CREATE 0x00
#define DRM_MY_NPU_BO_INFO 0x01
#define DRM_MY_NPU_SUBMIT 0x02
#define DRM_MY_NPU_WAIT 0x03
#define DRM_MY_NPU_GET_PARAM 0x04
#define DRM_IOCTL_MY_NPU_BO_CREATE \
DRM_IOWR(DRM_COMMAND_BASE + DRM_MY_NPU_BO_CREATE, \
struct drm_my_npu_bo_create)
/* 사용자 공간 호출 예시 */
struct drm_my_npu_bo_create create = {
.size = 1024 * 1024, /* 1MB 텐서 버퍼 */
.flags = MY_NPU_BO_CACHED,
};
ioctl(fd, DRM_IOCTL_MY_NPU_BO_CREATE, &create);
/* create.handle → GEM 핸들 */
/* create.npu_addr → NPU 가상 주소 */
NPU 메모리 관리 상세
NPU 메모리 관리는 크게 세 가지 측면으로 나뉩니다: GEM 오브젝트를 통한 버퍼 할당, IOMMU/PASID를 통한 주소 공간 격리, DMA-BUF를 통한 디바이스 간 공유입니다.
GEM 오브젝트 생명주기
GEM(Graphics Execution Manager) 오브젝트는 NPU에서 사용하는 모든 메모리 버퍼의 기본 단위입니다. 텐서 데이터, 모델 가중치, 커맨드 버퍼 모두 GEM BO(Buffer Object)로 관리됩니다.
/* GEM 오브젝트 생명주기 전체 흐름 */
/* 1. 생성 (ioctl) */
struct drm_my_npu_bo_create args = {
.size = tensor_size,
.flags = MY_NPU_BO_DEVICE_MEM, /* NPU 전용 메모리 */
};
ioctl(fd, DRM_IOCTL_MY_NPU_BO_CREATE, &args);
/* → 커널: drm_gem_shmem_create() → 페이지 할당
* → NPU MMU 매핑 → npu_addr 반환 */
/* 2. mmap (사용자 공간에서 CPU 접근) */
struct drm_my_npu_bo_info info = { .handle = args.handle };
ioctl(fd, DRM_IOCTL_MY_NPU_BO_INFO, &info);
void *ptr = mmap(NULL, args.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, info.mmap_offset);
/* → CPU에서 텐서 데이터 기록 */
memcpy(ptr, input_tensor_data, tensor_size);
/* 3. 잡에서 참조 (NPU가 npu_addr로 접근) */
struct drm_my_npu_submit submit = {
.cmd_handle = cmd_handle,
.bo_handles = bo_handles,
.bo_count = num_bos,
};
ioctl(fd, DRM_IOCTL_MY_NPU_SUBMIT, &submit);
/* 4. 완료 대기 */
struct drm_my_npu_wait wait = {
.handle = args.handle,
.timeout_ns = 5000000000ULL, /* 5초 */
};
ioctl(fd, DRM_IOCTL_MY_NPU_WAIT, &wait);
/* 5. 결과 읽기 (mmap된 포인터로 직접 접근) */
memcpy(output_data, ptr, tensor_size);
/* 6. 해제 */
munmap(ptr, args.size);
struct drm_gem_close close = { .handle = args.handle };
ioctl(fd, DRM_IOCTL_GEM_CLOSE, &close);
/* → 커널: NPU MMU 언매핑 → 페이지 해제 → GEM 오브젝트 소멸 */
IOMMU/PASID 통합
최신 NPU 드라이버는 시스템 IOMMU와 통합하여 프로세스별 주소 공간 격리를 구현합니다. PASID(Process Address Space ID)를 사용하면 여러 프로세스가 동일한 NPU를 공유하면서도 서로의 메모리에 접근할 수 없습니다.
/* PASID 기반 프로세스 격리 구현 (개념적 코드) */
struct my_npu_context {
struct drm_file *file;
u32 pasid; /* IOMMU PASID */
struct iommu_domain *domain; /* 프로세스별 IOMMU 도메인 */
struct drm_mm va_space; /* 프로세스별 VA 공간 */
};
static int my_npu_open(struct drm_device *dev,
struct drm_file *file)
{
struct my_npu_context *ctx;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
ctx->file = file;
/* PASID 할당 — IOMMU가 프로세스별 페이지 테이블 관리 */
ctx->pasid = iommu_sva_bind_device(dev->dev,
current->mm);
/* 프로세스별 VA 공간 초기화 */
drm_mm_init(&ctx->va_space, NPU_VA_START, NPU_VA_SIZE);
file->driver_priv = ctx;
return 0;
}
/* PASID 사용 시 메모리 접근 흐름:
* 1. 프로세스 A: ioctl(submit, pasid=10) → NPU가 PASID 10으로 DMA
* 2. IOMMU: PASID 10의 페이지 테이블에서 주소 변환
* 3. 프로세스 B의 메모리는 PASID 10에 매핑되지 않으므로 접근 불가
* → 하드웨어 수준 메모리 격리 달성 */
NPU 메모리 계층 구조
NPU 스케줄링(Scheduling)
NPU 워크로드 스케줄링은 drm_sched(DRM GPU Scheduler)를 재사용하여 구현됩니다.
이 스케줄러는 원래 GPU 잡 관리를 위해 설계되었지만, NPU의 컴퓨트 잡에도 동일하게 적용됩니다.
drm_sched 통합
drm_sched는 소프트웨어 큐(Software Queue)에서 하드웨어 큐(Hardware Queue)로 잡을 디스패치하는
프레임워크입니다. 다수의 프로세스가 NPU를 공유할 때 공정한 스케줄링과 타임아웃 복구를 제공합니다.
/* drm_sched 초기화 — probe 시 호출 */
static int my_npu_sched_init(struct my_npu_device *ndev)
{
int ret;
/* 스케줄러 생성:
* - ops: 잡 실행/타임아웃 콜백
* - num_rqs: 우선순위 큐 수 (MIN=1, MAX=3)
* - hw_submission: 동시 HW 실행 잡 수
* - hang_limit: 연속 타임아웃 허용 횟수
* - timeout: 잡 타임아웃 (jiffies)
* - name: 디버그용 이름 */
ret = drm_sched_init(&ndev->sched,
&my_npu_sched_ops,
DRM_SCHED_PRIORITY_COUNT, /* 우선순위 큐 */
16, /* HW 큐 깊이 */
0, /* hang limit */
msecs_to_jiffies(30000), /* 30초 타임아웃 */
NULL, /* timeout workq */
NULL, /* score */
"my_npu_sched",
ndev->dev);
return ret;
}
/* 잡 제출 흐름:
* 1. 유저 공간 → ioctl(SUBMIT) → my_npu_submit_ioctl()
* 2. drm_sched_job_init() → 잡 초기화
* 3. drm_sched_job_arm() → out_fence 생성
* 4. drm_sched_entity_push_job() → SW 큐에 삽입
* 5. drm_sched 스레드 → run_job() 콜백 → HW 레지스터 기록
* 6. HW 완료 IRQ → dma_fence_signal() → 유저에 통보 */
/* 잡 제출 ioctl 핸들러 */
static int my_npu_submit_ioctl(struct drm_device *dev,
void *data,
struct drm_file *file)
{
struct my_npu_submit *args = data;
struct my_npu_job *job;
struct my_npu_context *ctx = file->driver_priv;
int ret;
job = kzalloc(sizeof(*job), GFP_KERNEL);
/* 참조할 BO 목록 검증 + 잠금 */
ret = my_npu_validate_bos(job, args);
if (ret)
goto err;
/* drm_sched 잡 초기화 */
ret = drm_sched_job_init(&job->base,
&ctx->sched_entity,
1, /* credits */
ctx); /* owner */
if (ret)
goto err;
/* out_fence 준비 */
drm_sched_job_arm(&job->base);
/* syncobj에 fence 연결 (유저 공간 폴링/대기용) */
args->out_fence = drm_sched_job_get_fence(&job->base);
/* SW 큐에 삽입 → 스케줄러 스레드가 디스패치 */
drm_sched_entity_push_job(&job->base);
return 0;
err:
kfree(job);
return ret;
}
우선순위와 선점(Preemption)
drm_sched는 최대 세 단계의 우선순위 큐를 지원합니다.
높은 우선순위 큐의 잡이 먼저 디스패치되며, 하드웨어 선점을 지원하는 NPU에서는
실행 중인 낮은 우선순위 잡을 중단하고 높은 우선순위 잡을 먼저 실행할 수 있습니다.
| 우선순위 | 용도 | 타임아웃 | 선점 |
|---|---|---|---|
| HIGH (0) | 실시간 추론 (자율주행, 의료) | 5초 | 다른 잡 선점 가능 |
| NORMAL (1) | 일반 ML 추론 | 30초 | HIGH에 의해 선점됨 |
| LOW (2) | 배치 학습, 백그라운드 처리 | 120초 | 모든 상위 우선순위에 의해 선점 |
/* 프로세스별 스케줄링 엔티티(Entity) — 우선순위 지정 */
static int my_npu_ctx_create(struct my_npu_device *ndev,
struct my_npu_context *ctx,
enum drm_sched_priority priority)
{
struct drm_gpu_scheduler *scheds[] = { &ndev->sched };
return drm_sched_entity_init(&ctx->sched_entity,
priority,
scheds, 1, /* 스케줄러 수 */
NULL);
}
/* HW 선점 지원 NPU의 선점 콜백 (선택적) */
static void my_npu_preempt_job(struct my_npu_device *ndev)
{
/* 1. 현재 HW 실행 중인 잡 정지 */
writel(NPU_CMD_PREEMPT, ndev->mmio + NPU_REG_CTRL);
/* 2. HW 상태 저장 (중간 활성화 값, PC 등) */
my_npu_save_hw_state(ndev, &ndev->preempt_state);
/* 3. 선점 완료 인터럽트 대기 */
wait_for_completion_timeout(&ndev->preempt_done,
msecs_to_jiffies(100));
}
타임아웃과 에러 복구
NPU 잡이 타임아웃되면 drm_sched가 timedout_job 콜백을 호출합니다.
드라이버는 NPU 엔진을 리셋(Reset)하고, 실패한 잡의 fence를 에러로 시그널하며,
나머지 대기 중인 잡들을 재스케줄링합니다.
/* 에러 복구 시퀀스 */
static enum drm_gpu_sched_stat
my_npu_timedout_job(struct drm_sched_job *sched_job)
{
struct my_npu_job *job =
container_of(sched_job, struct my_npu_job, base);
struct my_npu_device *ndev = job_to_ndev(job);
dev_err(ndev->dev,
"NPU job timeout (ctx=%u, fence=%llu)\\n",
job->ctx_id, job->base.s_fence->finished.seqno);
/* 1. 실행 정지 */
writel(NPU_CMD_ABORT, ndev->mmio + NPU_REG_CTRL);
/* 2. HW 엔진 리셋 */
my_npu_engine_reset(ndev);
/* 3. 펌웨어 재로딩 (필요한 경우) */
if (ndev->needs_fw_reload)
my_npu_load_firmware(ndev);
/* 4. MMU 컨텍스트 재설정 */
my_npu_mmu_reset(ndev);
/* 5. devcoredump — 디버깅용 HW 상태 덤프 */
my_npu_devcoredump(ndev);
/* NOMINAL: 스케줄러 계속 동작 (재시작)
* ENODEV: 장치 회복 불가 → 스케줄러 정지 */
return DRM_GPU_SCHED_STAT_NOMINAL;
}
주요 Linux NPU 드라이버
| 드라이버 | 하드웨어 | 커널 경로 | 디바이스 노드 | 도입 버전 |
|---|---|---|---|---|
| intel_vpu (IVPU) | Intel Meteor Lake / Arrow Lake / Lunar Lake NPU | drivers/accel/ivpu/ |
/dev/accel/accel0 |
6.3 |
| habanalabs | Intel Gaudi / Gaudi2 / Gaudi3 (데이터센터 AI) | drivers/accel/habanalabs/ |
/dev/accel/accel0 |
6.2 (이전) |
| amdxdna | AMD Ryzen AI (XDNA / XDNA2 NPU) | drivers/accel/amdxdna/ |
/dev/accel/accel0 |
6.11 |
| qaic | Qualcomm Cloud AI 100 | drivers/accel/qaic/ |
/dev/accel/accel0 |
6.4 |
| etnaviv (NPU 확장) | Vivante VIP (i.MX 8M Plus 등) | drivers/gpu/drm/etnaviv/ |
/dev/dri/renderD* |
4.5+ |
| samsung_npu | Samsung Exynos NPU (모바일) | Out-of-tree (vendor kernel) | /dev/vertex* | — |
| mtk_apu | MediaTek APU (Dimensity SoC) | Out-of-tree (vendor kernel) | /dev/apusys* | — |
Intel NPU (IVPU) 드라이버 상세
Intel NPU(이전 명칭 VPU)는 Meteor Lake부터 Intel 클라이언트 프로세서에 통합된 AI 추론 가속기입니다.
drivers/accel/ivpu/에 위치하며, DRM Accel 프레임워크를 사용하는 대표적인 드라이버입니다.
- Meteor Lake (NPU 3720): 첫 통합 NPU, 최대 ~10 TOPS (INT8)
- Arrow Lake (NPU 3720): 아키텍처 유사, 소프트웨어 최적화
- Lunar Lake (NPU 4): 최대 ~48 TOPS, MCDM 아키텍처 대폭 개선
Intel NPU ioctl 인터페이스
| ioctl | 방향 | 설명 |
|---|---|---|
DRM_IOCTL_IVPU_GET_PARAM | R | HW 파라미터 조회 (플랫폼 타입, TOPS, 타일 수 등) |
DRM_IOCTL_IVPU_SET_PARAM | W | 런타임 파라미터 설정 |
DRM_IOCTL_IVPU_BO_CREATE | RW | GEM BO 생성 (크기, 플래그 → 핸들, NPU VA) |
DRM_IOCTL_IVPU_BO_INFO | RW | BO 정보 조회 (mmap offset, VA, 크기) |
DRM_IOCTL_IVPU_SUBMIT | W | 컴퓨트 잡 제출 (커맨드 버퍼 + BO 리스트) |
DRM_IOCTL_IVPU_BO_WAIT | RW | BO 사용 완료 대기 (타임아웃 지정) |
DRM_IOCTL_IVPU_METRIC_STREAMER_* | RW | 성능 메트릭 수집 (대역폭(Bandwidth), 활용률) |
AMD XDNA (Ryzen AI) 드라이버
AMD Ryzen AI 프로세서(Phoenix, Hawk Point, Strix Point)에 통합된 XDNA NPU입니다. FPGA 기반 AI Engine 타일로 구성되며, 각 타일은 VLIW 프로세서 + 벡터 유닛을 포함합니다.
- AI Engine 타일: 각 타일에 VLIW 스칼라 프로세서 + 512-bit 벡터 유닛 + 로컬 데이터 메모리(DM)
- 컬럼 기반 할당: 복수의 타일을 컬럼 단위로 묶어 워크로드에 할당 (리소스 파티셔닝)
- 데이터 흐름: 타일 간 스트림 인터커넥트로 파이프라인 구성 (ISA는 Xilinx Versal에서 유래)
- XDNA2 (Strix Point): Block FP (BFLOAT16 블록), 최대 ~50 TOPS
Qualcomm Cloud AI 100 (qaic) 드라이버
Qualcomm Cloud AI 100은 데이터센터 AI 추론 가속 카드입니다.
drivers/accel/qaic/에 위치하며, Linux 6.4에서 메인라인에 합류했습니다.
| 특성 | Cloud AI 100 Standard | Cloud AI 100 Ultra |
|---|---|---|
| INT8 성능 | ~200 TOPS | ~400 TOPS |
| 메모리 | 16GB LPDDR4X | 32GB LPDDR4X |
| TDP | 75W | 150W |
| 인터페이스 | PCIe Gen4 x16 | PCIe Gen4 x16 |
| 네트워크 코어 | 16 NSP | 32 NSP |
/* qaic 드라이버 핵심 ioctl (drivers/accel/qaic/) */
/* MHI(Modem Host Interface) 기반 호스트↔장치 통신 */
enum qaic_manage_msg_type {
QAIC_TRANS_PASSTHROUGH = 0, /* 장치 직접 메시지 */
QAIC_TRANS_DMA_XFER = 3, /* DMA 전송 설정 */
QAIC_TRANS_ACTIVATE_TO_DEV = 5, /* 모델 활성화 */
QAIC_TRANS_DEACTIVATE = 6, /* 모델 비활성화 */
QAIC_TRANS_STATUS_FROM_USR = 8, /* 상태 조회 */
};
/* 모델 배포 흐름:
* 1. MANAGE ioctl → 모델 바이너리를 장치에 전송
* 2. ACTIVATE ioctl → 모델을 NSP에 로드
* 3. ATTACH_SLICE_BO → 입출력 DMA 슬라이스 설정
* 4. EXECUTE_BO → 추론 실행 (입력→NPU→출력)
* 5. DETACH_SLICE_BO → 정리
* 6. DEACTIVATE → 모델 언로드 */
Etnaviv (Vivante VIP NPU)
NXP i.MX 8M Plus SoC에 통합된 Vivante VIP8000 NPU는 기존 etnaviv GPU 드라이버를 확장하여 지원합니다.
DRM Accel이 아닌 기존 DRM GPU 프레임워크(/dev/dri/renderD*)를 사용합니다.
- 성능: ~2.3 TOPS (INT8)
- 용도: 임베디드 ML 추론 (카메라 객체 인식, 음성 처리)
- 드라이버:
drivers/gpu/drm/etnaviv/(mainline) - 유저 공간: NXP eIQ, TFLite Delegate, ONNX Runtime EP
- 특징: SoC 내장으로 별도 PCIe 없이 AXI 버스로 연결, 저전력 추론에 적합
Intel Gaudi (habanalabs) 드라이버
데이터센터급 AI 학습/추론 가속기인 Intel Gaudi 시리즈를 지원합니다.
DRM Accel 프레임워크 도입 이전부터 존재하며, 6.2에서 drivers/accel/로 이동했습니다.
| 특성 | Gaudi | Gaudi2 | Gaudi3 |
|---|---|---|---|
| TPC (Tensor Processing Core) | 8개 | 24개 | 64개 |
| MME (Matrix Math Engine) | 2개 | 2개 | 8개 |
| HBM | 32GB HBM2 | 96GB HBM2E | 128GB HBM2E |
| 네트워크 | 10× 100GbE RoCE | 24× 100GbE RoCE | 24× 200GbE RoCE |
| BF16 성능 | ~370 TFLOPS | ~865 TFLOPS | ~1835 TFLOPS |
| FP8 성능 | — | ~1730 TFLOPS | ~3670 TFLOPS |
/* habanalabs 드라이버 핵심 구조 (간략화) */
struct hl_device {
struct drm_device drm;
const struct hl_asic_funcs *asic_funcs; /* ASIC별 함수 포인터 */
struct hl_cs_counters_atomic cs_cnt; /* 커맨드 서브미션 */
struct hl_vm vm; /* 메모리 관리 */
struct hl_nic *nic; /* 멀티 디바이스 통신 */
struct hwmon_ops *hwmon; /* 전원/열 관리 */
};
/* hl_asic_funcs: ASIC 추상화 인터페이스 */
struct hl_asic_funcs {
int (*hw_init)(struct hl_device *hdev);
void (*hw_fini)(struct hl_device *hdev, bool hard);
int (*suspend)(struct hl_device *hdev);
int (*resume)(struct hl_device *hdev);
int (*cb_mmap)(struct hl_device *hdev,
struct vm_area_struct *vma);
void (*ring_doorbell)(struct hl_device *hdev,
u32 hw_queue_id, u32 pi);
/* ... 50+ 함수 포인터 ... */
};
주요 NPU 하드웨어 비교
현재 리눅스에서 지원되거나 에코시스템에 영향을 주는 주요 NPU 하드웨어를 비교합니다. 클라우드(Cloud) 학습용 대형 가속기부터 모바일 SoC 내장 NPU까지 스펙트럼이 넓습니다.
Google TPU (Tensor Processing Unit)
Google TPU는 2016년 최초 공개된 데이터센터 전용 AI 가속기로, 시스톨릭 어레이 기반 설계의 대표적 사례입니다. TPU v4 Pod는 4,096개 칩을 3D 토러스(Torus) 토폴로지로 연결하여 엑사플롭스(ExaFLOPS) 급 성능을 제공합니다.
| 세대 | 연도 | 용도 | BF16/FP16 성능 | HBM | 상호 연결 |
|---|---|---|---|---|---|
| TPU v2 | 2017 | 학습/추론 | 45 TFLOPS (BF16) | 16GB HBM | ICI 2D 토러스 |
| TPU v3 | 2018 | 학습/추론 | 123 TFLOPS (BF16) | 32GB HBM | ICI 2D 토러스 |
| TPU v4 | 2021 | 학습/추론 | 275 TFLOPS (BF16) | 32GB HBM2e | ICI 3D 토러스 |
| TPU v5e | 2023 | 추론 최적화 | 197 TFLOPS (BF16) | 16GB HBM2e | ICI 2D 토러스 |
| TPU v5p | 2023 | 대규모 학습 | 459 TFLOPS (BF16) | 95GB HBM2e | ICI 3D 토러스 |
| TPU v6e (Trillium) | 2024 | 학습/추론 | 920 TFLOPS (BF16) | 32GB HBM | ICI 3D 토러스 |
Apple Neural Engine (ANE)
Apple Neural Engine은 A11 Bionic(2017)부터 Apple SoC에 통합된 NPU입니다. M3/M4 칩의 ANE는 16코어 구성으로 최대 18 TOPS(INT8) 성능을 제공합니다.
| 칩 | ANE 코어 | 성능 (TOPS) | 지원 데이터 타입 | 용도 |
|---|---|---|---|---|
| A11 Bionic | 2코어 | 0.6 | FP16 | 얼굴 인식 |
| A14 / M1 | 16코어 | 11 | FP16/INT8 | Core ML 추론 |
| A16 / M2 | 16코어 | 17 | FP16/INT8 | 실시간 이미지/비디오 처리 |
| A17 Pro / M3 | 16코어 | 35 | FP16/INT8 | 온디바이스 LLM |
| M4 | 16코어 | 38 | FP16/INT8 | Apple Intelligence |
Samsung Exynos NPU
Samsung Exynos SoC에 통합된 NPU는 Galaxy 스마트폰에서 카메라 AI, 음성 인식 등에 활용됩니다. Exynos 2400(Cortex-X4 기반)의 NPU는 최대 34.7 TOPS 성능을 제공합니다.
| 특성 | 상세 |
|---|---|
| 커널 드라이버 | Out-of-tree: /dev/vertex* 디바이스 노드 (vendor kernel) |
| 유저 공간 | Samsung ONE (On-device Neural Engine) SDK |
| 지원 모델 | ONNX, TFLite, Circle (Samsung 자체 포맷) |
| 양자화 | INT8/INT16/FP16, 혼합 정밀도 |
| 메인라인 현황 | 메인라인 기여 없음 — vendor kernel에서만 제공 |
NPU 하드웨어 종합 비교
| NPU | 타겟 | 피크 성능 | 메모리 | TDP | 리눅스 메인라인 |
|---|---|---|---|---|---|
| Intel NPU (Lunar Lake) | 클라이언트 PC | 48 TOPS | 시스템 DDR5 공유 | SoC 통합 | O (ivpu) |
| Intel Gaudi3 | 데이터센터 | 1835 TFLOPS (BF16) | 128GB HBM2E | 600W | O (habanalabs) |
| AMD XDNA2 | 클라이언트 PC | 50 TOPS | 시스템 DDR5 공유 | SoC 통합 | O (amdxdna) |
| Qualcomm HTP v75 | 모바일/PC | 75 TOPS | 시스템 LPDDR5 공유 | SoC 통합 | 부분 (fastrpc) |
| Google TPU v5p | 클라우드 | 459 TFLOPS (BF16) | 95GB HBM2e | ~250W | X (자체 스택) |
| Apple ANE (M4) | 클라이언트/모바일 | 38 TOPS | 통합 메모리 | SoC 통합 | X (비공개) |
| Samsung Exynos NPU | 모바일 | 34.7 TOPS | 시스템 LPDDR5 공유 | SoC 통합 | X (vendor) |
| Huawei Ascend 910B | 데이터센터 | 4096 TFLOPS (FP16) | 64GB HBM2e | 400W | X (비공개) |
Qualcomm QNN / HTP (Hexagon Tensor Processor)
Qualcomm은 Snapdragon SoC에 DSP 기반 AI 가속기를 통합해 왔습니다. 초기 Hexagon DSP에서 시작해, 현재는 HTP(Hexagon Tensor Processor)라는 독립적인 AI 코어로 진화했습니다. Snapdragon 8 Gen 3, Snapdragon X Elite 등 최신 칩에는 전용 NPU 클러스터가 탑재됩니다.
HTP 아키텍처와 진화
FastRPC 커널 드라이버
Qualcomm DSP/HTP는 FastRPC 메커니즘으로 유저 공간과 통신합니다.
FastRPC는 CPU↔DSP 간 함수 호출을 원격 프로시저 콜(RPC) 형태로 처리하며,
커널 드라이버 drivers/misc/fastrpc.c가 이를 중재합니다.
# FastRPC 관련 디바이스 노드
ls /dev/fastrpc-*
# /dev/fastrpc-cdsp → Compute DSP (AI 추론)
# /dev/fastrpc-adsp → Audio DSP
# /dev/fastrpc-mdsp → Modem DSP
# remoteproc를 통한 DSP 펌웨어 상태 확인
cat /sys/bus/platform/drivers/qcom-q6v5-pas/*/state
/* FastRPC 커널 핵심 구조 (drivers/misc/fastrpc.c) */
struct fastrpc_channel_ctx {
char *name; /* "cdsp", "adsp" 등 */
struct rpmsg_device *rpdev; /* rpmsg 채널 */
struct fastrpc_apps *apps;
spinlock_t lock;
struct idr ctx_idr; /* 컨텍스트 ID 관리 */
u32 dsp_cap_idx;
};
struct fastrpc_invoke_ctx {
int nscalars; /* 인자 수 */
struct fastrpc_map **maps; /* DMA buf 매핑 */
dma_addr_t buf; /* 인자 버퍼 */
struct completion work; /* 완료 대기 */
int retval; /* DSP 반환값 */
};
/* RPC 흐름:
* 1. 유저 공간 → ioctl(FASTRPC_IOCTL_INVOKE)
* 2. 커널: DMA 버퍼 고정(pin) + IOMMU 매핑
* 3. rpmsg로 DSP에 invoke 메시지 전송
* 4. DSP가 HTP에서 연산 실행
* 5. 완료 인터럽트 → completion 시그널
* 6. 유저 공간 복귀
*/
QNN SDK와 양자화
QNN(Qualcomm Neural Network) SDK는 ONNX/TFLite 모델을 HTP에서 실행 가능한 형태로 변환하고, INT4/INT8 양자화를 통해 추론 속도를 극대화합니다.
# ONNX 모델 → QNN 변환 (오프라인 컴파일)
qnn-onnx-converter \
--input_network mobilenet_v3.onnx \
--output_path mobilenet_v3.cpp \
--input_dim input "1,3,224,224"
# QNN 모델 → HTP 바이너리 (.serialized)
qnn-model-lib-generator \
-m mobilenet_v3.cpp \
-b libQnnHtp.so \
--output_dir ./output/
# 추론 성능 측정 (TOPS, 지연 시간)
qnn-net-run \
--model output/mobilenet_v3.serialized \
--backend libQnnHtp.so \
--input_list input_list.txt \
--duration 10
drivers/misc/fastrpc.c— mainline 포함 (Linux 5.4+)drivers/remoteproc/qcom_q6v5_pas.c— mainline 포함- QNN SDK 자체 — 비공개 바이너리 (Qualcomm 개발자 사이트 배포)
- HTP 펌웨어 — 바이너리 전용 (
/lib/firmware/qcom/) - ONNX Runtime QNN EP — 오픈소스 (Microsoft/Qualcomm 협력)
Huawei Ascend / CANN AI 플랫폼
Huawei Ascend는 대규모 AI 훈련(910 시리즈)과 에지 추론(310 시리즈)을 위해 설계된 자체 AI 칩 시리즈입니다. 핵심 연산 유닛인 DaVinci Core는 행렬 곱셈을 3D 큐브 방식으로 처리하여 고효율 AI 연산을 구현합니다.
Ascend 아키텍처: DaVinci Core
| 모델 | 용도 | 성능 | 메모리 | TDP |
|---|---|---|---|---|
| Ascend 910B | 대형 모델 훈련 (LLM) | 4096 TFLOPS (FP16) | HBM2e 64GB | 400W |
| Ascend 910 | 훈련 / 대규모 추론 | 1456 TFLOPS (FP16) | HBM2e 32GB | 310W |
| Ascend 310P | 에지 추론 (서버/엣지) | 256 TOPS (INT8) | LPDDR4X 8GB | 75W |
| Ascend 310 | 경량 추론 | 22 TOPS (INT8) | LPDDR4 8GB | 8W |
Ascend vs NVIDIA 생태계 비교
| 계층 | NVIDIA | Huawei Ascend |
|---|---|---|
| 하드웨어 | A100 / H100 / H200 | Ascend 910B / 310P |
| 커널 드라이버 | nvidia.ko (비공개) | ascend_drv.ko (비공개) |
| 런타임 API | CUDA Runtime | AscendCL |
| 수학 라이브러리 | cuBLAS / cuDNN | CANN NN Library |
| 컴파일러 | nvcc (CUDA C++) | ATC / TBE Compiler |
| 프레임워크 통합 | PyTorch CUDA | PyTorch torch_npu |
| 자체 프레임워크 | — | MindSpore (오픈소스) |
| 칩간 통신 | NVLink / NVSwitch | HCCS |
| 모니터링 | nvidia-smi | npu-smi |
| 메인라인 지원 | X (nouveau만 일부) | X (일부 HiSilicon 가속기만) |
CANN 런타임 구조
CANN(Compute Architecture for Neural Networks)은 Ascend 칩을 위한 소프트웨어 스택입니다. PyTorch/TensorFlow와 Ascend 하드웨어 사이를 중재하며, 커널 드라이버와 런타임 라이브러리로 구성됩니다.
# Atlas 800T A2 서버에서의 디바이스 확인
npu-smi info # NPU 상태 (nvidia-smi 유사)
npu-smi info -t usages -i 0 # 칩 0번 사용률
ls /dev/davinci* # DaVinci 디바이스 노드
# /dev/davinci0 ~ /dev/davinci7
# /dev/davinci_manager
# PyTorch + torch_npu 사용 예제
import torch
import torch_npu # Ascend 백엔드 플러그인
# NPU 디바이스 지정
device = torch.device("npu:0") # Ascend 910B 0번 칩
model = MyModel().to(device)
x = torch.randn(1, 3, 224, 224).to(device)
# AscendCL 내부 처리:
# 1. PyTorch Op → GE 그래프로 변환
# 2. 그래프 컴파일 → DaVinci ISA
# 3. /dev/davinci0 ioctl로 커널 드라이버 전달
# 4. DaVinci Core에서 3D Cube 연산 실행
output = model(x)
커널 드라이버 현황
- 비공개: Ascend 메인 드라이버 (
ascend_drv.ko,davinci_manager.ko) — Huawei 배포 바이너리 - 부분 공개:
drivers/char/hisi_acc_qm.c,drivers/crypto/hisilicon/— 암호화(Encryption) 가속기는 mainline - 오픈소스: torch_npu (GitHub), MindSpore (오픈소스 AI 프레임워크)
- 제약 요인: 미국 수출 규제로 인한 독립 개발 경로 → mainline 기여 일부 차단
# Atlas 200I DK (개발 키트)에서의 추론 예제
# .om 파일 = Ascend 오프라인 모델 (atc 컴파일러 출력)
# 1. ONNX → Ascend .om 변환
atc --model=resnet50.onnx \
--framework=5 \
--output=resnet50 \
--input_shape="input:1,3,224,224" \
--soc_version=Ascend310P3
# 2. .om 모델 추론 실행
python3 acl_run.py --model resnet50.om
# 커널 DMA 흐름:
# CPU 메모리 → DMA → HBM2e (가중치/입력)
# DaVinci Core 연산 → HBM2e (출력)
# DMA → CPU 메모리 (결과 수집)
NPU 전원 관리(Power Management)
NPU는 항상 활성 상태일 필요가 없으므로, Runtime PM을 통한 공격적인 전원 관리가 중요합니다. 특히 노트북/모바일 SoC에서는 추론이 없을 때 NPU를 완전히 꺼서 전력을 절약합니다.
static int my_npu_runtime_suspend(struct device *dev)
{
struct my_npu_device *ndev = dev_get_drvdata(dev);
my_npu_wait_idle(ndev); /* 잡 완료 대기 */
my_npu_mmu_save_state(ndev); /* MMU 컨텍스트 저장 */
clk_disable_unprepare(ndev->clk); /* 클럭 게이팅 */
my_npu_power_off(ndev); /* 전원 도메인 OFF */
return 0;
}
static int my_npu_runtime_resume(struct device *dev)
{
struct my_npu_device *ndev = dev_get_drvdata(dev);
int ret;
my_npu_power_on(ndev); /* 전원 ON */
clk_prepare_enable(ndev->clk); /* 클럭 활성화 */
ret = my_npu_load_firmware(ndev); /* FW 재로딩 */
if (ret)
return ret;
my_npu_mmu_restore_state(ndev); /* MMU 복원 */
return 0;
}
static const struct dev_pm_ops my_npu_pm_ops = {
SET_RUNTIME_PM_OPS(my_npu_runtime_suspend,
my_npu_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(my_npu_system_suspend,
my_npu_system_resume)
};
NPU와 DMA-BUF: 디바이스 간 버퍼 공유
DMA-BUF를 통해 NPU, GPU, 카메라(ISP), 비디오 디코더 간에 제로카피 버퍼 공유가 가능합니다. 이는 카메라 → NPU → GPU → 디스플레이 같은 ML 파이프라인에서 중요한 역할을 합니다.
/* NPU에서 DMA-BUF 익스포트 (exporter 역할) */
static struct dma_buf *
my_npu_gem_prime_export(struct drm_gem_object *obj, int flags)
{
struct dma_buf_export_info exp_info = {
.exp_name = "my_npu",
.owner = obj->dev->driver->fops->owner,
.ops = &my_npu_dmabuf_ops,
.size = obj->size,
.flags = flags,
.priv = obj,
};
return drm_gem_dmabuf_export(obj->dev, &exp_info);
}
/* GPU에서 NPU의 DMA-BUF를 임포트 (importer 역할) */
struct drm_gem_object *
gpu_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf)
{
struct dma_buf_attachment *attach;
struct sg_table *sgt;
attach = dma_buf_attach(dma_buf, dev->dev);
if (IS_ERR(attach))
return ERR_CAST(attach);
sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
if (IS_ERR(sgt)) {
dma_buf_detach(dma_buf, attach);
return ERR_CAST(sgt);
}
/* sg_table을 GPU 페이지 테이블에 매핑
* → NPU가 쓴 결과를 GPU가 직접 읽기 가능 */
return gpu_create_gem_from_sgt(dev, sgt, dma_buf->size);
}
DMA-BUF 동기화 프로토콜
DMA-BUF를 통해 NPU와 다른 디바이스가 버퍼를 공유할 때, 접근 순서를 보장하기 위한
동기화 프로토콜이 필수입니다. dma_fence와 dma_resv(Reservation Object)가
이 역할을 담당합니다.
/* DMA-BUF 공유 동기화 예시:
* Camera ISP → NPU → GPU 파이프라인 */
/* 1. ISP가 프레임 캡처 완료 → fence 시그널 */
struct dma_fence *isp_fence = isp_capture_frame(buf);
dma_resv_add_fence(buf->resv, isp_fence,
DMA_RESV_USAGE_WRITE);
/* 2. NPU가 DMA-BUF 임포트 → ISP fence 대기 */
struct dma_fence *npu_in_fence;
npu_in_fence = dma_resv_get_singleton(buf->resv,
DMA_RESV_USAGE_WRITE);
/* drm_sched이 npu_in_fence 완료를 기다린 후 NPU 잡 디스패치 */
drm_sched_job_add_dependency(&npu_job->base, npu_in_fence);
/* 3. NPU 추론 완료 → 자체 fence 시그널 */
dma_resv_add_fence(buf->resv, npu_done_fence,
DMA_RESV_USAGE_WRITE);
/* 4. GPU가 같은 DMA-BUF 임포트 → NPU fence 대기 후 렌더링 */
struct dma_fence *gpu_in_fence;
gpu_in_fence = dma_resv_get_singleton(buf->resv,
DMA_RESV_USAGE_WRITE);
/* GPU 스케줄러가 대기 후 오버레이 렌더링 */
DMA-BUF Heap과 NPU 전용 힙
NPU 드라이버는 DMA-BUF Heap 인터페이스를 통해 특수한 메모리 유형(예: CMA 연속 메모리, 보안 메모리)을 할당할 수 있습니다.
# DMA-BUF Heap 디바이스 확인
ls /dev/dma_heap/
# system → 일반 시스템 메모리
# cma → 연속 물리 메모리 (CMA)
# npu-secure → NPU 보안 메모리 (TrustZone 보호, 벤더별)
# DMA-BUF 할당 예시 (사용자 공간)
# int heap_fd = open("/dev/dma_heap/system", O_RDONLY);
# struct dma_heap_allocation_data alloc = {
# .len = 1024 * 1024, // 1MB
# .fd_flags = O_CLOEXEC,
# };
# ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &alloc);
# // alloc.fd → DMA-BUF fd, NPU에 임포트 가능
NPU 커널 설정 (Kconfig)
# DRM Accel 기본 지원
CONFIG_DRM=m # DRM 코어 (필수)
CONFIG_DRM_ACCEL=y # Accel 서브시스템 활성화
# Intel NPU (IVPU)
CONFIG_DRM_ACCEL_IVPU=m # Intel VPU/NPU 드라이버
# Intel Gaudi (habanalabs)
CONFIG_DRM_ACCEL_HABANALABS=m # Gaudi/Gaudi2/Gaudi3
# AMD XDNA (Ryzen AI)
CONFIG_DRM_ACCEL_AMDXDNA=m # AMD XDNA NPU
# Qualcomm Cloud AI
CONFIG_DRM_ACCEL_QAIC=m # Qualcomm AIC100
# 공통 의존성
CONFIG_DRM_GEM_SHMEM_HELPER=m # GEM shmem 헬퍼
CONFIG_DRM_SCHED=m # GPU/NPU 스케줄러
CONFIG_DMA_SHARED_BUFFER=y # DMA-BUF (디바이스 간 공유)
CONFIG_FW_LOADER=y # 펌웨어 로더 (필수)
CONFIG_IOMMU_API=y # IOMMU 지원
# Qualcomm FastRPC (DSP/HTP)
CONFIG_FASTRPC=m # Qualcomm FastRPC 드라이버
CONFIG_RPMSG=y # 원격 프로세서 메시징
CONFIG_REMOTEPROC=m # DSP 펌웨어 로딩
# Etnaviv (Vivante VIP NPU)
CONFIG_DRM_ETNAVIV=m # Vivante GPU/NPU
# 디버깅 지원
CONFIG_DRM_ACCEL_DEBUG=y # Accel debugfs 확장
CONFIG_DRM_SCHED_DEBUG=y # 스케줄러 디버그 출력
CONFIG_DEV_COREDUMP=y # 디바이스 코어 덤프 (크래시 분석)
CONFIG_DEBUG_FS=y # debugfs 활성화
- Ubuntu 24.04 LTS: intel-npu-driver PPA 제공, AMD XDNA DKMS 패키지
- Fedora 41+: 커널 6.11+ 기본 포함 (ivpu, amdxdna, qaic)
- RHEL 9.4+: habanalabs 백포트 포함 (Intel Gaudi 지원)
- Android 14+: Vendor kernel에서 NPU HAL 제공
NPU 드라이버 모듈 파라미터
# Intel NPU (ivpu) 모듈 파라미터 확인
modinfo intel_vpu | grep parm
# parm: test_mode:Enable test mode (int)
# parm: dbg_mask:Debug message mask (int)
# 디버그 메시지 활성화
modprobe intel_vpu dbg_mask=0xff
# habanalabs 모듈 파라미터
modinfo habanalabs | grep parm
# parm: timeout_locked:Timeout for locked operations in seconds (int)
# parm: reset_on_lockup:Reset device on lockup (int)
# AMD XDNA 모듈 파라미터
modprobe amdxdna debug_mask=0x1f
NPU 디버깅(Debugging)
sysfs / debugfs 인터페이스
# Accel 디바이스 확인
ls -la /dev/accel/
ls -la /sys/class/accel/
# Intel NPU debugfs
cat /sys/kernel/debug/accel/0/info
cat /sys/kernel/debug/accel/0/fw_version
cat /sys/kernel/debug/accel/0/busy_time_us
cat /sys/kernel/debug/accel/0/freq
# Runtime PM 상태 확인
cat /sys/class/accel/accel0/device/power/runtime_status
# HW 센서 (hwmon)
cat /sys/class/accel/accel0/device/hwmon/*/temp1_input
cat /sys/class/accel/accel0/device/hwmon/*/power1_input
# AMD XDNA sysfs
cat /sys/class/accel/accel0/device/npu_clk_freq
cat /sys/class/accel/accel0/device/npu_power_state
NPU 디버깅 도구
| 도구 | 용도 | 사용법 |
|---|---|---|
dmesg | NPU 드라이버 로그 | dmesg | grep -i "ivpu\|npu\|accel\|xdna" |
lspci -vvv | NPU PCI 디바이스 정보 | lspci -d ::0x1200 -vvv (Processing Accel class) |
ftrace | DRM/Accel 내부 추적 | echo drm:* > /sys/kernel/debug/tracing/set_event |
perf | 드라이버 성능 분석 | perf record -a -g -e drm:* -- sleep 5 |
| Intel NPU Plugin | OpenVINO 통합 프로파일링 | OpenVINO benchmark_app + NPU 플러그인 |
| xrt-smi | AMD XDNA 모니터링 | Xilinx Runtime CLI (xrt-smi examine) |
NPU 유저 공간 소프트웨어 스택
NPU 드라이버는 커널에서 HW 접근과 메모리 관리만 담당하고, 실제 ML 모델 컴파일/실행은 유저 공간 스택이 처리합니다.
| 계층 | Intel NPU | AMD XDNA | Qualcomm Cloud AI |
|---|---|---|---|
| ML 프레임워크 | OpenVINO, ONNX Runtime | ONNX Runtime, PyTorch | Qualcomm AI Engine Direct |
| 컴파일러 | NPU Compiler (VPUX) | Vitis AI / XDNA Compiler | QNN SDK |
| UMD (런타임) | intel-npu-acceleration-library | XRT (Xilinx Runtime) | libqaic |
| KMD (커널) | ivpu (drivers/accel/ivpu/) | amdxdna (drivers/accel/amdxdna/) | qaic (drivers/accel/qaic/) |
# 1. 모델 벤치마크
benchmark_app -m model.onnx -d NPU -hint latency
# 2. Python API로 추론
import openvino as ov
core = ov.Core()
model = core.compile_model("model.onnx", "NPU")
result = model.infer_new_request({"input": input_data})
사용자 공간 mmap 인터페이스
NPU 드라이버에서 GEM BO를 사용자 공간에 매핑하는 과정은 GPU 드라이버와 동일한 DRM mmap 인프라를 사용합니다. NPU에서는 텐서 데이터 기록/읽기에 이 인터페이스를 활용합니다.
/* 커널 측: GEM mmap 핸들러 */
static int my_npu_gem_mmap(struct drm_gem_object *obj,
struct vm_area_struct *vma)
{
struct my_npu_bo *bo = to_npu_bo(obj);
/* 캐시 모드 설정:
* - WC (Write-Combining): 큰 순차 전송에 적합 (텐서 입력)
* - WB (Write-Back): CPU에서 자주 읽는 경우 (결과 읽기)
* - UC (Uncacheable): MMIO 레지스터 접근 */
switch (bo->cache_mode) {
case NPU_BO_WC:
vma->vm_page_prot =
pgprot_writecombine(vma->vm_page_prot);
break;
case NPU_BO_CACHED:
/* 기본 WB 캐시 — CPU 캐시 플러시 필요 */
break;
}
return drm_gem_shmem_mmap(&bo->base, vma);
}
/* 사용자 공간 전체 예시: 텐서 입력 → NPU 추론 → 결과 읽기 */
int run_inference(int fd, const float *input,
float *output, size_t size)
{
struct drm_my_npu_bo_create in_create = {
.size = size,
.flags = MY_NPU_BO_INPUT | MY_NPU_BO_WC,
};
struct drm_my_npu_bo_create out_create = {
.size = size,
.flags = MY_NPU_BO_OUTPUT | MY_NPU_BO_CACHED,
};
/* 입출력 버퍼 생성 */
ioctl(fd, DRM_IOCTL_MY_NPU_BO_CREATE, &in_create);
ioctl(fd, DRM_IOCTL_MY_NPU_BO_CREATE, &out_create);
/* mmap으로 CPU 접근 */
struct drm_my_npu_bo_info in_info = {
.handle = in_create.handle
};
ioctl(fd, DRM_IOCTL_MY_NPU_BO_INFO, &in_info);
float *in_ptr = mmap(NULL, size,
PROT_WRITE, MAP_SHARED,
fd, in_info.mmap_offset);
/* 입력 텐서 기록 */
memcpy(in_ptr, input, size);
/* 잡 제출 */
uint32_t handles[] = {
in_create.handle, out_create.handle
};
struct drm_my_npu_submit submit = {
.cmd_handle = cmd_handle,
.bo_handles = (uintptr_t)handles,
.bo_count = 2,
};
ioctl(fd, DRM_IOCTL_MY_NPU_SUBMIT, &submit);
/* 완료 대기 */
struct drm_my_npu_wait wait = {
.handle = out_create.handle,
.timeout_ns = 10000000000ULL,
};
ioctl(fd, DRM_IOCTL_MY_NPU_WAIT, &wait);
/* 결과 읽기 */
struct drm_my_npu_bo_info out_info = {
.handle = out_create.handle
};
ioctl(fd, DRM_IOCTL_MY_NPU_BO_INFO, &out_info);
float *out_ptr = mmap(NULL, size,
PROT_READ, MAP_SHARED,
fd, out_info.mmap_offset);
memcpy(output, out_ptr, size);
/* 정리 */
munmap(in_ptr, size);
munmap(out_ptr, size);
/* GEM close... */
return 0;
}
컴파일러 스택과 커널 드라이버 연계
NPU 컴파일러 스택은 ML 모델을 NPU 하드웨어에서 실행 가능한 바이너리로 변환하는 과정을 담당합니다. 이 과정에서 커널 드라이버와의 인터페이스가 명확히 정의되어야 합니다.
NPU 컴파일 파이프라인
MLIR과 NPU 컴파일러
MLIR(Multi-Level Intermediate Representation)은 LLVM 프로젝트의 일부로, 다양한 추상화 수준의 중간 표현을 지원하는 컴파일러 인프라입니다. 최신 NPU 컴파일러들은 MLIR을 기반으로 구축됩니다.
// MLIR에서 NPU 코드 생성까지의 변환 단계 (개념적 예시)
// 1단계: high-level (linalg 방언) — ONNX에서 변환
func.func @conv2d(%input: tensor<1x3x224x224xf32>,
%weight: tensor<64x3x7x7xf32>) -> tensor<1x64x112x112xf32> {
%result = linalg.conv_2d_nchw_fchw
ins(%input, %weight : tensor<1x3x224x224xf32>, tensor<64x3x7x7xf32>)
outs(%init : tensor<1x64x112x112xf32>) -> tensor<1x64x112x112xf32>
return %result : tensor<1x64x112x112xf32>
}
// 2단계: tiled (타일링 적용) — SRAM 크기에 맞게 분할
// → 64×64 타일 단위로 분할, 더블 버퍼링 삽입
// 3단계: lowered (NPU 전용 방언)
// → MAC 어레이 명령, DMA 전송, 동기화 배리어
// 4단계: codegen (NPU 바이너리)
// → 커맨드 버퍼 (.blob) 생성 → GEM BO에 적재 → ioctl(SUBMIT)
Apache TVM 통합
Apache TVM은 다양한 하드웨어 백엔드를 지원하는 오픈소스 ML 컴파일러입니다. NPU 벤더들은 TVM에 자체 백엔드를 추가하여 모델 컴파일을 지원합니다.
# Apache TVM으로 NPU 코드 생성 예시
import tvm
from tvm import relay
# 1. ONNX 모델 로드
import onnx
model = onnx.load("mobilenet_v3.onnx")
mod, params = relay.frontend.from_onnx(model)
# 2. NPU 타겟 설정
target = tvm.target.Target("npu -device=my_npu")
# 3. 컴파일 (양자화 + 타일링 + 코드 생성)
with tvm.transform.PassContext(opt_level=3):
# 양자화 적용
mod = relay.quantize.quantize(mod, params)
# NPU 전용 최적화 패스
lib = relay.build(mod, target=target, params=params)
# 4. NPU에서 실행
dev = tvm.device("npu", 0)
module = tvm.contrib.graph_executor.GraphModule(
lib["default"](dev))
module.set_input("input", input_data)
module.run()
output = module.get_output(0).numpy()
# 내부 흐름:
# Relay IR → TIR (Tensor IR) → NPU 전용 스케줄
# → 커맨드 버퍼 생성 → /dev/accel/accel0 ioctl
Android NNAPI 연동
NNAPI(Neural Networks API)는 Android에서 NPU/GPU/DSP 가속을 위한 표준 API입니다. 리눅스 커널의 NPU 드라이버가 Android HAL(Hardware Abstraction Layer)을 통해 NNAPI와 연결됩니다.
| 계층 | 구성 요소 | 역할 |
|---|---|---|
| 앱 | TFLite / ML Kit | 모델 로드, 추론 요청 |
| 프레임워크 | NNAPI Runtime | Op 지원 확인, 벤더 드라이버 선택 |
| HAL | Vendor NNAPI HAL | 모델 컴파일, 버퍼 관리 |
| 유저 드라이버 | UMD (Vendor) | ioctl 호출, 커맨드 제출 |
| 커널 | NPU KMD | GEM BO, drm_sched, DMA, IRQ |
| 하드웨어 | NPU 칩 | 텐서 연산 실행 |
멀티 NPU 구성과 분산 추론
데이터센터 환경에서는 여러 NPU를 동시에 활용하여 대규모 모델을 분산 처리합니다. 커널 드라이버는 각 NPU 인스턴스를 독립적으로 관리하면서, 디바이스 간 동기화를 지원합니다.
멀티 인스턴스 관리
# 다중 NPU 디바이스 확인
ls /dev/accel/
# accel0 accel1 accel2 accel3
# 각 디바이스의 PCIe 정보
for dev in /sys/class/accel/accel*; do
echo "$(basename $dev): $(readlink -f $dev/device)"
done
# 디바이스별 상태 확인
for i in 0 1 2 3; do
echo "=== accel$i ==="
cat /sys/kernel/debug/accel/$i/fw_version 2>/dev/null
cat /sys/class/accel/accel$i/device/power/runtime_status
done
디바이스 간 P2P DMA
PCIe P2P(Peer-to-Peer) DMA를 사용하면 CPU 메모리를 경유하지 않고 NPU 간 직접 데이터를 전송할 수 있습니다. 분산 학습에서 그래디언트(Gradient) 교환에 활용됩니다.
/* PCIe P2P DMA 전송 (NPU 0 → NPU 1) */
static int my_npu_p2p_transfer(
struct my_npu_device *src_ndev,
struct my_npu_device *dst_ndev,
struct my_npu_bo *src_bo,
struct my_npu_bo *dst_bo)
{
struct pci_dev *src_pdev = src_ndev->pdev;
struct pci_dev *dst_pdev = dst_ndev->pdev;
int ret;
/* P2P 거리 확인 — 같은 루트 컴플렉스인지 */
ret = pci_p2pdma_distance(src_pdev, dst_pdev, true);
if (ret < 0) {
dev_warn(src_ndev->dev,
"P2P not supported — fallback to host bounce\\n");
return my_npu_bounce_transfer(src_ndev, dst_ndev,
src_bo, dst_bo);
}
/* DMA-BUF를 통한 P2P:
* src_bo → DMA-BUF export → dst에서 import
* → dst의 IOMMU가 src의 물리 주소를 직접 매핑 */
return my_npu_dmabuf_p2p(src_ndev, dst_ndev, src_bo, dst_bo);
}
NPU 토폴로지 인식
| 토폴로지 | 구성 | 대역폭 | 사례 |
|---|---|---|---|
| PCIe 직접 연결 | CPU ↔ NPU (PCIe switch 경유) | PCIe Gen5: ~64 GB/s | Intel Gaudi, AMD MI300 |
| 전용 인터커넥트 | NPU ↔ NPU (전용 링크) | 100~400 GB/s | Gaudi RoCE, HCCS |
| SoC 내부 버스 | NPU ↔ CPU (AXI/NoC) | 수십 GB/s | Intel NPU, AMD XDNA |
| CXL 연결 | CPU ↔ NPU (CXL.cache/mem) | CXL 3.0: ~128 GB/s | 차세대 NPU (개발 중) |
NPU 전원 관리 상세
DVFS (Dynamic Voltage and Frequency Scaling)
NPU는 워크로드 강도에 따라 동적으로 전압과 주파수를 조절합니다. 커널의 OPP(Operating Performance Points) 프레임워크와 연동하여 전력 효율을 극대화합니다.
/* NPU DVFS OPP 테이블 예시 (Device Tree) */
/*
npu_opp_table: npu-opp-table {
compatible = "operating-points-v2";
opp-200000000 {
opp-hz = /bits/ 64 <200000000>; // 200 MHz
opp-microvolt = <750000>; // 0.75V
opp-level = <0>; // Low power
};
opp-400000000 {
opp-hz = /bits/ 64 <400000000>; // 400 MHz
opp-microvolt = <850000>; // 0.85V
opp-level = <1>; // Normal
};
opp-800000000 {
opp-hz = /bits/ 64 <800000000>; // 800 MHz
opp-microvolt = <950000>; // 0.95V
opp-level = <2>; // Performance
};
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>; // 1.2 GHz
opp-microvolt = <1050000>; // 1.05V
opp-level = <3>; // Max
};
};
*/
/* DVFS 전환 로직 */
static int my_npu_set_freq(struct my_npu_device *ndev,
unsigned long freq_hz)
{
struct dev_pm_opp *opp;
int ret;
/* 요청 주파수에 가장 가까운 OPP 검색 */
opp = dev_pm_opp_find_freq_ceil(ndev->dev, &freq_hz);
if (IS_ERR(opp))
return PTR_ERR(opp);
/* 전압 + 주파수 동시 전환 */
ret = dev_pm_opp_set_opp(ndev->dev, opp);
dev_pm_opp_put(opp);
if (!ret)
dev_dbg(ndev->dev,
"NPU frequency set to %lu Hz\\n", freq_hz);
return ret;
}
/* 워크로드 기반 자동 DVFS */
static void my_npu_dvfs_work(struct work_struct *work)
{
struct my_npu_device *ndev =
container_of(work, struct my_npu_device, dvfs_work.work);
u32 util = readl(ndev->mmio + NPU_REG_UTILIZATION);
unsigned long new_freq;
if (util > 90)
new_freq = ndev->max_freq; /* 고부하 → 최대 */
else if (util > 50)
new_freq = ndev->mid_freq; /* 중부하 */
else if (util > 10)
new_freq = ndev->low_freq; /* 저부하 */
else
new_freq = ndev->min_freq; /* 유휴 */
my_npu_set_freq(ndev, new_freq);
/* 100ms 후 다시 확인 */
schedule_delayed_work(&ndev->dvfs_work,
msecs_to_jiffies(100));
}
열 관리(Thermal Management)
NPU 열 관리는 커널의 thermal 프레임워크와 통합됩니다. NPU가 과열되면 cooling device로 등록된 DVFS 드라이버가 주파수를 낮추고, 심한 경우 NPU를 완전히 정지시킵니다.
/* NPU cooling device 등록 */
static int my_npu_cooling_get_max_state(
struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = 3; /* 4단계: 0=풀성능, 1=75%, 2=50%, 3=최저 */
return 0;
}
static int my_npu_cooling_set_cur_state(
struct thermal_cooling_device *cdev,
unsigned long state)
{
struct my_npu_device *ndev = cdev->devdata;
static const unsigned long freq_table[] = {
1200000000, /* state 0: 1.2 GHz (풀성능) */
800000000, /* state 1: 800 MHz */
400000000, /* state 2: 400 MHz */
200000000, /* state 3: 200 MHz (최저) */
};
return my_npu_set_freq(ndev, freq_table[state]);
}
static const struct thermal_cooling_device_ops npu_cooling_ops = {
.get_max_state = my_npu_cooling_get_max_state,
.get_cur_state = my_npu_cooling_get_cur_state,
.set_cur_state = my_npu_cooling_set_cur_state,
};
/* probe에서 cooling device 등록 */
ndev->cdev = thermal_of_cooling_device_register(
of_node, "npu", ndev, &npu_cooling_ops);
전력 도메인(Power Domain)
SoC 내장 NPU는 독립적인 전력 도메인에 배치되어 다른 IP 블록과 무관하게 전원을 제어할 수 있습니다.
리눅스의 genpd(Generic Power Domain) 프레임워크가 이를 관리합니다.
/* Device Tree 전력 도메인 바인딩 예시 */
npu@12000000 {
compatible = "vendor,my-npu-v1";
reg = <0x12000000 0x10000>;
interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu_npu CLK_NPU_CORE>,
<&cmu_npu CLK_NPU_AXI>;
clock-names = "core", "axi";
/* 전력 도메인 연결 */
power-domains = <&pd_npu>;
/* OPP 테이블 참조 */
operating-points-v2 = <&npu_opp_table>;
/* IOMMU 연결 */
iommus = <&smmu 0x100>;
/* 전원 관리 */
status = "okay";
};
| 상태 | 전력 소비 | 복귀 시간 | 설명 |
|---|---|---|---|
| D0 (Active) | 100% TDP | 즉시 | 잡 실행 중 |
| D0 Idle | ~30% TDP | 마이크로초 | 클럭 게이팅(Clock Gating), 잡 대기 |
| D3 Hot | ~5% TDP | 밀리초 | 전원 도메인 OFF, 컨텍스트 보존 |
| D3 Cold | ~0% | 수십 밀리초 | 완전 전원 차단, FW 재로딩 필요 |
NPU 프로파일링과 성능 분석
커널 트레이스포인트(Tracepoint)
NPU 드라이버는 커널 트레이스포인트를 통해 잡 제출, 실행, 완료 이벤트를 추적할 수 있습니다.
ftrace나 perf로 수집하여 성능 병목을 분석합니다.
/* NPU 트레이스포인트 정의 (include/trace/events/npu.h) */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM npu
TRACE_EVENT(npu_job_submit,
TP_PROTO(struct my_npu_job *job),
TP_ARGS(job),
TP_STRUCT__entry(
__field(u32, ctx_id)
__field(u64, fence_seqno)
__field(u32, cmd_size)
__field(u32, bo_count)
),
TP_fast_assign(
__entry->ctx_id = job->ctx_id;
__entry->fence_seqno = job->base.s_fence->finished.seqno;
__entry->cmd_size = job->cmd_bo->base.base.size;
__entry->bo_count = job->bo_count;
),
TP_printk("ctx=%u fence=%llu cmd_size=%u bos=%u",
__entry->ctx_id, __entry->fence_seqno,
__entry->cmd_size, __entry->bo_count)
);
TRACE_EVENT(npu_job_done,
TP_PROTO(struct my_npu_job *job, s64 elapsed_ns),
TP_ARGS(job, elapsed_ns),
TP_STRUCT__entry(
__field(u32, ctx_id)
__field(u64, fence_seqno)
__field(s64, elapsed_ns)
),
TP_fast_assign(
__entry->ctx_id = job->ctx_id;
__entry->fence_seqno = job->base.s_fence->finished.seqno;
__entry->elapsed_ns = elapsed_ns;
),
TP_printk("ctx=%u fence=%llu elapsed=%lld ns",
__entry->ctx_id, __entry->fence_seqno,
__entry->elapsed_ns)
);
# ftrace로 NPU 이벤트 추적
echo npu > /sys/kernel/debug/tracing/set_event
cat /sys/kernel/debug/tracing/trace_pipe
# 출력 예시:
# npu_sched-42 [003] .... 1234.567: npu_job_submit: ctx=1 fence=42 cmd_size=4096 bos=3
# <idle>-0 [002] d..1 1234.572: npu_job_done: ctx=1 fence=42 elapsed=5234000 ns
# perf로 NPU 성능 통계 수집
perf stat -e 'npu:npu_job_submit' \
-e 'npu:npu_job_done' \
-a -- sleep 10
# perf record로 NPU 잡 타임라인 캡처
perf record -e 'npu:*' -a -- benchmark_app -m model.onnx -d NPU
perf script # 타임스탬프 기반 분석
debugfs 인터페이스 상세
/* NPU debugfs 파일 등록 */
static void my_npu_debugfs_init(struct drm_minor *minor)
{
struct my_npu_device *ndev = minor_to_ndev(minor);
struct dentry *root = minor->debugfs_root;
/* 하드웨어 정보 */
debugfs_create_file("hw_info", 0444, root,
ndev, &npu_hw_info_fops);
/* 펌웨어 버전 */
debugfs_create_file("fw_version", 0444, root,
ndev, &npu_fw_version_fops);
/* 활성 잡 목록 */
debugfs_create_file("active_jobs", 0444, root,
ndev, &npu_active_jobs_fops);
/* 성능 카운터 */
debugfs_create_file("perf_counters", 0444, root,
ndev, &npu_perf_counters_fops);
/* 메모리 사용량 */
debugfs_create_file("mem_usage", 0444, root,
ndev, &npu_mem_usage_fops);
/* DVFS 상태 */
debugfs_create_file("dvfs_state", 0444, root,
ndev, &npu_dvfs_state_fops);
/* 에러 로그 */
debugfs_create_file("error_log", 0444, root,
ndev, &npu_error_log_fops);
}
# debugfs를 통한 NPU 상태 모니터링
# HW 정보
cat /sys/kernel/debug/accel/0/hw_info
# Platform: Meteor Lake NPU
# Tiles: 2 NCE
# INT8 TOPS: 48
# FW version: 4.2.1
# 활성 잡 확인
cat /sys/kernel/debug/accel/0/active_jobs
# Job 42: ctx=1 priority=NORMAL submitted=1234.567 elapsed=2.3ms
# Job 43: ctx=2 priority=HIGH submitted=1234.570 elapsed=0.8ms
# 성능 카운터
cat /sys/kernel/debug/accel/0/perf_counters
# mac_utilization: 87%
# dma_bandwidth: 12.4 GB/s
# sram_hit_rate: 94.2%
# jobs_completed: 1234
# jobs_failed: 0
# 메모리 사용량
cat /sys/kernel/debug/accel/0/mem_usage
# Total GEM objects: 42
# Total size: 256 MB
# VA space used: 180 MB / 4 GB
# devcoredump (크래시 덤프)
# NPU 잡 타임아웃 시 자동 생성
ls /sys/class/devcoredump/devcd*/
cat /sys/class/devcoredump/devcd0/data > npu_crash.dump
하드웨어 성능 카운터
| 카운터 | 설명 | 최적화 지침 |
|---|---|---|
| MAC Utilization | MAC 어레이 활용률 (%) | <70%: 타일링/패딩(Padding) 비효율. 컴파일러 최적화 필요 |
| DMA Bandwidth | DDR↔SRAM 전송 대역폭 | 피크 대비 <50%: 더블 버퍼링 미적용 또는 텐서 레이아웃 문제 |
| SRAM Hit Rate | SRAM 재사용률 | <80%: 타일 크기 조정 필요. 데이터 재사용 패턴 개선 |
| Stall Cycles | 데이터 대기 클럭 | 높음: DMA 프리페치 타이밍 조정. 파이프라인 스톨 분석 |
| Power Consumption | 실시간 전력 소비 (mW) | TDP 초과 시 자동 쓰로틀링(Throttling) |
| Temperature | 다이 온도 (°C) | Tj,max 접근 시 DVFS 다운 또는 잡 정지 |
NPU 향후 전망
CXL 연결 NPU
CXL(Compute Express Link)은 CPU와 가속기 간 캐시 일관성(Cache Coherence)을 유지하면서 고대역폭 메모리 공유를 가능하게 하는 인터커넥트 기술입니다. CXL 3.0 이상에서는 NPU가 CPU 메모리를 직접 접근하면서도 캐시 일관성을 자동으로 유지할 수 있어, DMA 전송과 캐시 플러시 오버헤드가 크게 줄어듭니다.
| 특성 | 기존 PCIe NPU | CXL NPU |
|---|---|---|
| 메모리 접근 | DMA 전송 (명시적) | load/store (투명) |
| 캐시 일관성 | SW 관리 (flush/invalidate) | HW 자동 (CXL.cache) |
| 지연 시간 | 마이크로초 (DMA 설정 포함) | 나노초 (load/store) |
| 메모리 풀링 | 불가 (디바이스별 분리) | 가능 (CXL.mem 공유 풀) |
| 커널 영향 | DMA API + IOMMU | mm 서브시스템 + DAX + CXL 드라이버 |
칩렛(Chiplet) 기반 NPU 설계
칩렛 기반 설계는 NPU 타일을 독립적인 다이(Die)로 제조한 뒤 패키징 수준에서 결합하는 방식입니다. AMD XDNA가 이미 AI Engine 타일을 칩렛 형태로 통합하고 있으며, Intel도 Foveros/EMIB 패키징으로 NPU 칩렛을 SoC에 통합하고 있습니다.
- UCIe(Universal Chiplet Interconnect Express): 칩렛 간 표준 인터페이스. NPU 칩렛을 다양한 SoC에 재사용 가능
- 스케일링: 칩렛 수를 늘려 NPU 성능을 선형적으로 확장. 에지(4 타일) → 서버(64 타일) 동일 아키텍처
- 커널 영향: 멀티 다이 구성에서 NUMA(Non-Uniform Memory Access) 인식 스케줄링 필요. drm_sched의 멀티 스케줄러 인스턴스로 타일별 독립 큐 관리
NPU 보안 고려사항
NPU를 멀티테넌트(Multi-Tenant) 환경에서 운영할 때 보안이 중요합니다. 다음은 커널 드라이버 수준에서 고려해야 할 보안 요소들입니다.
| 위협 | 대응 메커니즘 | 구현 위치 |
|---|---|---|
| 메모리 격리 위반 | PASID 기반 주소 공간 분리, IOMMU 2단계 변환 | KMD + IOMMU 드라이버 |
| 악성 커맨드 버퍼 | 커맨드 스트림 검증(Validation), MMIO 접근 제한 | KMD (ioctl 핸들러) |
| 펌웨어 변조 | 서명 검증(Secure Boot), 펌웨어 무결성 검사 | KMD + 하드웨어 RoT |
| 사이드 채널 공격 | 타이밍 격리, 공유 캐시 파티셔닝 | 하드웨어 + 펌웨어 |
| 서비스 거부(DoS) | 잡 타임아웃, 리소스 제한(cgroup), 공정 스케줄링 | drm_sched + KMD |
| 모델 유출 | 보안 메모리(TrustZone), 모델 암호화 | TEE + KMD |
/* 커맨드 버퍼 검증 예시 */
static int my_npu_validate_cmdbuf(struct my_npu_device *ndev,
struct my_npu_bo *cmd_bo,
struct my_npu_context *ctx)
{
u32 *cmds = cmd_bo->vaddr;
u32 cmd_count = cmd_bo->base.base.size / sizeof(u32);
u32 i;
for (i = 0; i < cmd_count; ) {
u32 opcode = cmds[i] & NPU_CMD_OPCODE_MASK;
u32 size = (cmds[i] >> 16) & 0xFFFF;
switch (opcode) {
case NPU_CMD_DMA_LOAD:
case NPU_CMD_DMA_STORE:
/* DMA 주소가 이 컨텍스트의 VA 공간 내인지 확인 */
if (!my_npu_va_in_range(ctx, cmds[i+1], cmds[i+2]))
return -EINVAL;
break;
case NPU_CMD_COMPUTE:
/* 허용된 연산 유형인지 확인 */
break;
case NPU_CMD_MMIO_WRITE:
/* 직접 MMIO 쓰기는 금지 */
dev_warn(ndev->dev,
"Rejected MMIO write in cmdbuf\\n");
return -EPERM;
default:
return -EINVAL;
}
i += size;
}
return 0;
}
cgroup 기반 NPU 리소스 제한
향후 NPU 리소스 관리를 위한 cgroup 컨트롤러가 논의되고 있습니다. 현재는 DRM fdinfo를 통한 프로세스별 NPU 사용량 모니터링이 지원됩니다.
# DRM fdinfo를 통한 프로세스별 NPU 사용량 확인
# (프로세스가 /dev/accel/accel0을 열고 있을 때)
cat /proc/<PID>/fdinfo/<FD>
# drm-driver: intel_vpu
# drm-client-id: 42
# drm-engine-npu: 1234567890 ns
# drm-memory-vram: 256 MiB
# drm-cycles-npu: 9876543210
# 전체 NPU 사용 프로세스 목록
for pid in /proc/[0-9]*; do
for fd in $pid/fd/*; do
if readlink $fd 2>/dev/null | grep -q accel; then
echo "PID=$(basename $pid): $(readlink $fd)"
cat $pid/fdinfo/$(basename $fd) 2>/dev/null | grep drm-
fi
done
done
새로운 기술 동향
| 기술 | 설명 | 커널 영향 |
|---|---|---|
| PIM/PNM | Processing-in/near-Memory. 메모리 내부 연산 | 새로운 디바이스 클래스, HBM-PIM 드라이버 |
| 광학(Photonic) NPU | 광학 행렬 곱셈. 전력 효율 극대화 | 새로운 디바이스 모델, 아날로그 제어 인터페이스 |
| 뉴로모픽(Neuromorphic) | 스파이킹 신경망(SNN) 전용 칩 | 이벤트 기반 스케줄링, 비동기 실행 모델 |
| FP4/MX 포맷 | 4비트 부동소수점, 마이크로스케일링 | 새로운 데이터 타입 지원, 컴파일러 양자화 확장 |
| 통합 가속기 API | SYCL/oneAPI 기반 NPU+GPU 통합 프로그래밍 | 크로스 디바이스 동기화, 공유 VA 공간 |
| 에지 LLM | 온디바이스 대규모 언어 모델 추론 | INT4/FP4 지원, 대용량 KV 캐시 관리, 메모리 압박 |
- drm_sched 개선: 공정 스케줄링, 우선순위 기반 선점, 멀티 큐 지원 확대
- CXL 통합: CXL.cache/mem을 활용한 coherent 메모리 접근
- 보안 강화: 프로세스별 격리(PASID), 펌웨어 검증(Secure Boot)
- 표준화: uAPI 안정화, 크로스 벤더 호환 가능한 추론 인터페이스
- 관측성: perf/ftrace 통합, 표준 성능 카운터 인터페이스
참고 사항
drivers/gpu/drm/— DRM 코어 + 모든 GPU 드라이버drivers/gpu/drm/scheduler/— drm_sched (GPU/NPU 스케줄러)drivers/accel/— DRM Accel (NPU/AI 가속기 드라이버)drivers/accel/ivpu/— Intel NPU (IVPU) 드라이버drivers/accel/habanalabs/— Intel Gaudi 드라이버drivers/accel/amdxdna/— AMD XDNA NPU 드라이버drivers/accel/qaic/— Qualcomm Cloud AI 100 드라이버drivers/misc/fastrpc.c— Qualcomm FastRPC (DSP/HTP 통신)drivers/gpu/drm/etnaviv/— Vivante VIP (NPU 기능 포함)include/drm/— DRM 헤더 파일include/drm/drm_accel.h— Accel 서브시스템 APIinclude/drm/gpu_scheduler.h— drm_sched APIinclude/uapi/drm/— 유저 공간 API (ioctl, 구조체(Struct))include/uapi/drm/ivpu_accel.h— Intel NPU ioctl 정의include/uapi/drm/amdxdna_accel.h— AMD XDNA ioctl 정의include/uapi/drm/habanalabs_accel.h— Gaudi ioctl 정의drivers/dma-buf/— DMA-BUF 프레임워크drivers/iommu/— IOMMU 프레임워크 (PASID 포함)Documentation/gpu/— 커널 공식 GPU 문서Documentation/accel/— Accel 서브시스템 문서Documentation/accel/introduction.rst— Accel 소개 및 설계 원칙
- Kernel Docs — Compute Accelerators — DRM Accel 서브시스템 공식 문서, 설계 원칙
- Kernel Docs — DRM GPU Scheduler — NPU 잡 스케줄링에 사용되는 drm_sched 문서
- Kernel Docs — DMA-BUF — NPU↔GPU↔ISP 간 제로카피 버퍼 공유 API
- Kernel Docs — request_firmware — NPU 펌웨어 로딩 API
- Kernel Docs — DRM Internals — GEM, ioctl 디스패치, DRM 코어 API
- Intel NPU Driver (유저스페이스) — Intel NPU 유저스페이스 드라이버, OpenVINO 통합
- Intel OpenVINO Documentation — NPU 백엔드 지원, 모델 최적화 가이드
- ONNX Runtime — NPU 실행 프로바이더(EP) 지원, 크로스 플랫폼 추론
- Qualcomm AI Engine Direct (QNN) — Qualcomm HTP/DSP NPU SDK
- Huawei Ascend CANN Documentation — Ascend NPU 개발 문서, DaVinci 아키텍처
- dri-devel 메일링 리스트 — DRM/Accel 커널 패치 및 리뷰 토론
- LWN.net — Accelerators — NPU/AI 가속기 관련 심층 기술 기사
- IGT GPU Tools — DRM Accel 드라이버 테스트 프레임워크
- Fence 시그널링 규칙 —
dma_fence는 반드시 유한 시간 내에 시그널되어야 합니다. 무한 대기 fence는 시스템 전체를 교착시킬 수 있습니다 - 메모리 매핑(Mapping) 주의 — NPU 메모리를 WC(Write-Combining)로 매핑할 때 캐시 일관성(Cache Coherency)에 주의.
ioremap_wc()사용 - NPU 전원 관리 — NPU는 유휴 시 Runtime PM으로 완전 전원 차단을 권장. 펌웨어 재로딩 비용과 절전 효과의 트레이드오프 고려
- 펌웨어 로딩 — 최신 NPU는 대부분
request_firmware()로 마이크로코드를 로딩합니다. initramfs에 펌웨어 포함 필요 (CONFIG_EXTRA_FIRMWARE) - DMA-BUF 공유 — NPU ↔ GPU ↔ ISP 간 제로카피 버퍼 공유 시 디바이스 간 캐시 일관성 보장 필요 (CPU 캐시 플러시(Flush))
- NPU MMU와 IOMMU — NPU 자체 MMU와 시스템 IOMMU는 별개. 두 단계 변환이 모두 적용되므로 디버깅 시 주소 변환 경로 추적 필수
- uAPI 안정성 —
include/uapi/drm/에 정의된 ioctl 구조체는 한번 릴리스되면 ABI로 고정됩니다. 새 필드 추가 시 반드시 확장 가능한 구조(flags, reserved 필드)로 설계 - 동시성 제어 — GEM 핸들 테이블, MMU 페이지 테이블, 잡 큐 접근 시 적절한 잠금(mutex/spinlock) 필수. drm_sched는 자체 잠금을 관리하지만 드라이버 내부 상태는 별도 보호 필요
NPU 드라이버 개발 모범 사례
메인라인 NPU 드라이버를 개발할 때 참고할 모범 사례를 정리합니다.
| 항목 | 필수 여부 | 설명 |
|---|---|---|
| DRM Accel 프레임워크 사용 | 권장 | GEM, drm_sched, DMA-BUF 인프라 재사용으로 코드 중복 방지 |
| uAPI 헤더 분리 | 필수 | include/uapi/drm/에 ioctl 구조체 정의, ABI 안정성 보장 |
| Runtime PM 구현 | 필수 | 유휴 시 전원 차단, autosuspend_delay 적절히 설정 (1~10초) |
| 펌웨어 로딩 | 필수 | request_firmware() 사용, /lib/firmware/에 FW 배치 |
| debugfs 제공 | 권장 | HW 정보, 활성 잡, 성능 카운터, 에러 로그 |
| devcoredump | 권장 | HW 크래시 시 레지스터 덤프 생성 (dev_coredumpv()) |
| hwmon 통합 | 권장 | 온도, 전력, 주파수 센서를 hwmon으로 노출 |
| DRM fdinfo | 권장 | 프로세스별 NPU 사용량 (engine time, memory) 보고 |
| 에러 복구 | 필수 | 잡 타임아웃 시 HW 리셋 + FW 재로딩 + 스케줄러 재시작 |
| 커맨드 검증 | 보안 | 유저 제출 커맨드 버퍼의 opcode/주소 범위 검증 |
| Documentation | 필수 | Documentation/accel/에 드라이버 문서 작성 |
| 셀프테스트 | 권장 | tools/testing/selftests/에 ioctl 테스트 추가 |
# NPU 드라이버 셀프테스트 실행 예시
cd tools/testing/selftests/accel/
make
./run_tests.sh
# 또는 kselftest 프레임워크로 실행
make -C tools/testing/selftests TARGETS=accel run_tests
# 개별 테스트:
# test_accel_open — /dev/accel/accel0 열기/닫기
# test_bo_create — GEM BO 생성/삭제
# test_bo_mmap — BO mmap + 읽기/쓰기
# test_submit — 잡 제출 + 완료 대기
# test_dmabuf — DMA-BUF 내보내기/가져오기
# test_error_inject — 에러 주입 + 복구 확인
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.