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 스택 구축에 필요한 핵심 내용을 다룹니다.

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

핵심 요약

  • 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)을 분석하고 모델/드라이버를 최적화합니다.

단계별 이해

  1. NPU 하드웨어 아키텍처 파악 — MAC(Multiply-Accumulate) 어레이, 온칩 SRAM, DMA 엔진 등 NPU 내부 구성요소의 역할과 데이터 흐름을 이해합니다.

    GPU가 범용 SIMT(Single Instruction Multiple Thread) 구조인 반면, NPU는 특정 연산 패턴에 최적화된 데이터플로 아키텍처를 사용합니다.

  2. DRM Accel 드라이버 구조 분석drm_accel_register()로 등록하고, GEM(Graphics Execution Manager) 객체로 메모리를 관리하며, ioctl로 작업을 제출하는 구조를 파악합니다.

    /dev/accel/accel0을 통해 유저 공간 런타임이 모델 로드, 추론 요청, 결과 수신을 수행합니다.

  3. 모델 컴파일과 펌웨어 로드 흐름 추적 — 고수준 모델을 NPU 바이너리로 컴파일하고, 드라이버가 request_firmware()로 펌웨어를 로드한 뒤 작업 큐에 추론 명령을 제출하는 과정을 따라갑니다.

    컴파일 단계에서 연산 그래프 분할, 메모리 할당 계획, 명령어 스케줄링이 결정되므로 런타임 성능에 직접적 영향을 미칩니다.

  4. DMA와 메모리 관리 점검 — 호스트-NPU 간 텐서 데이터 전송에 dma-buf 공유, CMA 할당, IOMMU 매핑이 올바르게 설정되었는지 확인합니다.

    제로 카피(Zero-copy) 경로가 가능한 경우 dma-buf import/export로 카메라→NPU→디스플레이 파이프라인을 구성합니다.

  5. 성능 프로파일링과 전력 관리 — NPU 하드웨어 카운터와 타임스탬프로 추론 지연을 측정하고, Runtime PM으로 유휴 시 클록/전원을 절감합니다.

    배치 크기, 양자화(Quantization) 수준, 클록 주파수를 조합하여 지연-처리량-전력 삼각 균형을 최적화합니다.

NPU vs GPU: NPU는 딥러닝 추론/학습에 특화된 하드웨어 가속기로, GPU와 달리 행렬 곱셈(GEMM)과 저정밀도 연산(INT8/FP16)에 최적화되어 있습니다. GPU는 범용 병렬 처리(CUDA/OpenCL)를 지원하는 반면, NPU는 컴파일러가 생성한 고정 함수 파이프라인으로 동작하여 특정 추론 워크로드에서 더 높은 전력 효율을 보이는 경우가 많습니다. 메인라인에서 도입된 DRM Accel 서브시스템(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 가속기를 관리하며, 커널 버전별 구체 기능은 릴리스/배포판 정책을 확인해야 합니다.

DRM Accel vs DRM GPU: DRM Accel은 DRM 인프라(GEM, DMA-BUF, drm_sched 등)를 재사용하지만, 렌더 노드(/dev/dri/renderD*)와 별도의 /dev/accel/accel* 디바이스 노드를 사용합니다. 이를 통해 GPU용 유저 공간 도구(Mesa, Vulkan)와 NPU용 ML 프레임워크가 충돌 없이 공존할 수 있습니다.

NPU 설계 접근 방식: ASIC vs FPGA

NPU 하드웨어는 크게 두 가지 설계 방식으로 나뉩니다.

특성ASIC 기반 NPUFPGA 기반 NPU
설계 유연성고정 — 제조 후 변경 불가재구성 가능(Reconfigurable) — 비트스트림 교체
성능높음 — 전용 실리콘 최적화중간 — 라우팅 오버헤드
전력 효율최고 (TOPS/W)ASIC 대비 3~10배 높은 소비전력
개발 비용수억~수십억 원 (마스크 비용)낮음 — HDL 수정만으로 반복
시장 출시12~24개월수 주~수 개월
대표 사례Google TPU, Apple ANE, Intel NPUAMD XDNA (Versal 기반), Xilinx DPU
리눅스 드라이버전용 드라이버 (ivpu, habanalabs)FPGA 관리자 + 오버레이 (fpga_mgr + accel)
하이브리드 접근: AMD XDNA는 FPGA에서 유래한 AI Engine 타일을 ASIC SoC에 통합한 하이브리드 설계입니다. AI Engine의 명령어 집합(ISA)은 Xilinx Versal ACAP에서 유래했지만, 실리콘은 완전한 ASIC으로 제조됩니다. 이를 통해 FPGA의 프로그래밍 유연성과 ASIC의 전력 효율을 동시에 달성합니다.

데이터플로우 아키텍처(Dataflow Architecture)

NPU의 핵심 설계 원리는 데이터플로우 아키텍처입니다. CPU의 폰 노이만(Von Neumann) 모델이 명령어 스트림을 중심으로 실행 순서를 결정하는 반면, 데이터플로우 아키텍처는 데이터의 가용성(Data Availability)에 따라 연산을 트리거합니다.

폰 노이만 모델 (CPU) 명령어 메모리 데이터 메모리 제어 유닛 (순차 실행) Fetch → Decode → Execute → Writeback ALU (범용 연산 유닛) 병목: 메모리 대역폭, 분기 예측 미스 강점: 임의 제어 흐름, 분기 처리 TOPS/W: ~0.1 (범용) 데이터플로우 모델 (NPU) 입력 텐서 가중치 시스톨릭 어레이 (Systolic Array) PE PE PE PE PE PE PE PE ← 데이터가 준비되면 자동으로 연산 시작 활성화 함수 + 후처리 병목: 온칩 SRAM 크기, DMA 대역폭 강점: 행렬 곱셈, 고정 패턴 데이터 흐름 TOPS/W: 10~50+ (전용 실리콘)

데이터플로우 아키텍처의 핵심 요소는 다음과 같습니다:

시스톨릭 어레이(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의 시스톨릭 어레이 사양
NPU어레이 크기데이터 타입클럭당 MAC피크 TOPS (INT8)
Google TPU v4128×128 × 4 코어BF16/INT865,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 DaVinci16×16×16 3D 큐브FP16/INT84,096 (FP16)~512

NPU vs GPU vs CPU 연산 특성

특성CPUGPUNPU
아키텍처범용 파이프라인, 소수의 고성능 코어대량의 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는 다음과 같은 공통 구조를 가집니다.

Host Interface PCIe / AXI Mailbox Command Queue IRQ Controller FW Loader MMU/IOMMU Compute Cluster (NCE / Tile) MAC Array (Systolic Array) INT8/FP16 GEMM Activation ReLU/Sigmoid/ SoftMax/Pool SRAM Scratchpad Memory DMA Engine Tile ↔ DDR/HBM Firmware / Microcontroller Task Scheduler Layer Dispatch Power Manager DVFS / Clock Gate External Memory DDR / LPDDR / HBM 모델 가중치 + 입출력 텐서 DMA-BUF 공유 가능 User Space Stack ML Framework (TFLite/ONNX) UMD (User Mode Driver) libdrm / ioctl /dev/accel/accel0

NPU 데이터 흐름

NPU에서 추론을 수행하는 전체 데이터 흐름은 다음과 같습니다.

  1. 모델 컴파일: ONNX/TFLite 모델을 NPU 전용 바이너리(blob)로 오프라인 컴파일. 레이어별 타일링, 양자화, 메모리 할당 계획이 포함됨
  2. 버퍼 할당: GEM BO(Buffer Object)를 통해 입력/출력/가중치 버퍼를 커널 드라이버에 할당 요청
  3. 커맨드 버퍼 생성: UMD가 DMA 전송, 연산 커맨드 등을 커맨드 버퍼에 기록
  4. 잡 제출: ioctl을 통해 커맨드 버퍼를 커널에 제출 → drm_sched가 스케줄링
  5. HW 실행: 펌웨어가 레이어 단위로 Compute Cluster에 디스패치(Dispatch), SRAM ↔ DDR DMA 전송 관리
  6. 완료 통보: 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와 연산이 겹쳐 파이프라인 효율 극대화
 */
레이어 융합(Layer Fusion): NPU 컴파일러는 연속된 레이어를 하나로 합쳐 중간 결과를 DDR에 기록하지 않고 SRAM에 유지합니다. 예를 들어 Conv2D → BatchNorm → ReLU를 하나의 융합 연산으로 처리하면 DDR 접근이 3분의 1로 줄어들어 성능과 전력 효율이 크게 향상됩니다. 이 최적화는 커널 드라이버가 아닌 오프라인 컴파일러가 수행하며, 커널은 융합된 연산이 포함된 커맨드 버퍼를 그대로 HW에 전달합니다.

양자화(Quantization)와 데이터 타입

NPU는 전력 효율을 위해 낮은 정밀도의 데이터 타입을 사용합니다. MAC 어레이의 데이터 타입에 따라 연산 처리량(TOPS)이 크게 달라집니다.

데이터 타입비트 폭범위/정밀도용도상대 처리량
FP3232-bitIEEE 754 단정밀도학습 기준선1× (기준)
FP1616-bit±65504, 정밀도 ~3.3자리학습/추론 혼합
BF1616-bitFP32 동적 범위, 낮은 정밀도학습 (기울기 안정성)
INT88-bit-128~127 / 0~255추론 최적화 (PTQ/QAT)
INT44-bit-8~7 / 0~15LLM 가중치 압축
FP8 (E4M3/E5M2)8-bitE4M3: ±448 / E5M2: ±57344차세대 학습/추론
양자화 기법:
  • 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_driverDRIVER_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;
}
NPU MMU와 시스템 IOMMU: NPU 자체 MMU와 시스템 IOMMU(예: ARM SMMU, Intel VT-d)는 별개입니다. 시스템 IOMMU는 NPU 디바이스의 DMA 접근을 물리 메모리(Physical Memory) 보호 관점에서 제한하고, NPU MMU는 디바이스 내부의 가상 주소 변환(Address Translation)을 담당합니다. 두 단계 변환이 모두 적용됩니다.

Accel 서브시스템 상세

DRM Accel 서브시스템은 Linux 6.2에서 Oded Gabbay에 의해 도입되었습니다. 기존 DRM 인프라를 재사용하면서도 GPU와 독립된 디바이스 네임스페이스를 제공하여, ML 프레임워크와 그래픽 스택이 충돌하지 않도록 설계되었습니다.

DRM/Accel 프레임워크 아키텍처 User Space Mesa / Vulkan OpenVINO / ONNX RT XRT / libhgai libdrm / ioctl Kernel Space /dev/dri/renderD128 /dev/accel/accel0 DRM 코어 (drivers/gpu/drm/) drm_drv.c — 드라이버 등록 drm_file.c — 파일 오퍼레이션 drm_ioctl.c — ioctl 디스패치 drm_prime.c — DMA-BUF 공유 인프라 drm_gem.c — GEM 오브젝트 drm_sched.c — GPU/NPU 스케줄러 drm_mm.c — VA 할당자 dma_fence.c — 동기화 drm_accel.c DRIVER_COMPUTE_ACCEL 플래그 GPU 드라이버 i915, amdgpu, nouveau NPU 드라이버 ivpu, habanalabs, amdxdna 기타 Accel qaic

Accel vs misc device vs platform driver 접근 방식

NPU 커널 드라이버를 구현할 때 선택할 수 있는 세 가지 주요 접근 방식이 있습니다.

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 메모리 계층 구조 레지스터 파일 (PE 내부) ~수 KB | 1 클럭 접근 | 최고 대역폭 온칩 SRAM (스크래치패드 메모리) 64KB~4MB | 1~3 클럭 | 수백 GB/s~수 TB/s | SW 관리 (컴파일러가 타일링) 공유 L2 SRAM / 클러스터 캐시 1MB~16MB | 5~20 클럭 | 타일 간 데이터 공유 | 파이프라인 스테이징 외부 메모리 (DDR5/LPDDR5/HBM2e) 8GB~128GB | 50~200 클럭 | 25~3000 GB/s | 모델 가중치, 입출력 텐서, DMA-BUF 공유 호스트 시스템 메모리 (CPU DDR) PCIe DMA 전송 | 전처리/후처리 | CPU↔NPU 데이터 교환 빠름 느림 작음
타일링(Tiling) 전략: NPU 컴파일러는 큰 텐서를 온칩 SRAM에 맞는 작은 타일로 분할합니다. 예를 들어 1024×1024 행렬을 64×64 타일로 나누면, 각 타일이 SRAM에 적재되어 고속 처리됩니다. DMA 엔진이 다음 타일을 사전 적재(Prefetch)하면서 현재 타일을 연산하는 더블 버퍼링(Double Buffering) 기법으로 메모리 지연을 숨깁니다. 이 타일링 계획은 커널 드라이버가 아닌 오프라인 컴파일러가 결정하며, 결과가 커맨드 버퍼에 인코딩되어 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_schedtimedout_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 프레임워크를 사용하는 대표적인 드라이버입니다.

drivers/accel/ivpu/ 구성 ivpu_drv.c / ivpu_drv.h (드라이버 진입점, 디바이스 구조체) 실행/펌웨어 경로 ivpu_hw.c (IP별 HW 추상화) ivpu_ipc.c, ivpu_jsm_msg.c (IPC/JSM) ivpu_fw.c, ivpu_pm.c (부팅/전원) 잡/메모리 경로 ivpu_job.c (drm_sched 기반 잡 처리) ivpu_gem.c (GEM BO 관리) ivpu_mmu.c, ivpu_mmu_context.c 관측/디버깅 ivpu_debugfs.c 상태/펌웨어/큐 정보를 debugfs로 제공
Intel NPU 세대별 특성:
  • 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_PARAMRHW 파라미터 조회 (플랫폼 타입, TOPS, 타일 수 등)
DRM_IOCTL_IVPU_SET_PARAMW런타임 파라미터 설정
DRM_IOCTL_IVPU_BO_CREATERWGEM BO 생성 (크기, 플래그 → 핸들, NPU VA)
DRM_IOCTL_IVPU_BO_INFORWBO 정보 조회 (mmap offset, VA, 크기)
DRM_IOCTL_IVPU_SUBMITW컴퓨트 잡 제출 (커맨드 버퍼 + BO 리스트)
DRM_IOCTL_IVPU_BO_WAITRWBO 사용 완료 대기 (타임아웃 지정)
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 프로세서 + 벡터 유닛을 포함합니다.

drivers/accel/amdxdna/ 구성 aie2_pci.c + aie2_pm.c (PCIe 초기화/전원 관리) 런타임 실행 경로 aie2_ctx.c / amdxdna_ctx.c aie2_solver.c (리소스 파티션) amdxdna_gem.c (BO 관리) amdxdna_mailbox.c (호스트↔FW 통신) 제어/관측 경로 aie2_message.c (ERT 메시지 프로토콜) amdxdna_sysfs.c (sysfs 디버그 속성) 드라이버 상태, 성능, 오류 관찰 지점
AMD XDNA 아키텍처 특징:
  • 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 StandardCloud AI 100 Ultra
INT8 성능~200 TOPS~400 TOPS
메모리16GB LPDDR4X32GB LPDDR4X
TDP75W150W
인터페이스PCIe Gen4 x16PCIe Gen4 x16
네트워크 코어16 NSP32 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*)를 사용합니다.

Intel Gaudi (habanalabs) 드라이버

데이터센터급 AI 학습/추론 가속기인 Intel Gaudi 시리즈를 지원합니다. DRM Accel 프레임워크 도입 이전부터 존재하며, 6.2에서 drivers/accel/로 이동했습니다.

특성GaudiGaudi2Gaudi3
TPC (Tensor Processing Core)8개24개64개
MME (Matrix Math Engine)2개2개8개
HBM32GB HBM296GB HBM2E128GB HBM2E
네트워크10× 100GbE RoCE24× 100GbE RoCE24× 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) 급 성능을 제공합니다.

Google TPU 세대별 비교
세대연도용도BF16/FP16 성능HBM상호 연결
TPU v22017학습/추론45 TFLOPS (BF16)16GB HBMICI 2D 토러스
TPU v32018학습/추론123 TFLOPS (BF16)32GB HBMICI 2D 토러스
TPU v42021학습/추론275 TFLOPS (BF16)32GB HBM2eICI 3D 토러스
TPU v5e2023추론 최적화197 TFLOPS (BF16)16GB HBM2eICI 2D 토러스
TPU v5p2023대규모 학습459 TFLOPS (BF16)95GB HBM2eICI 3D 토러스
TPU v6e (Trillium)2024학습/추론920 TFLOPS (BF16)32GB HBMICI 3D 토러스
TPU와 리눅스: Google TPU는 자체 운영체제 스택을 사용하며, 메인라인 리눅스 드라이버는 제공하지 않습니다. 사용자는 Google Cloud TPU VM을 통해 접근하며, JAX/TensorFlow가 XLA 컴파일러를 거쳐 TPU에 잡을 제출합니다. 다만 TPU의 시스톨릭 어레이 설계는 이후 등장한 Intel Gaudi, AMD XDNA 등 메인라인 지원 NPU에 큰 영향을 주었습니다.

Apple Neural Engine (ANE)

Apple Neural Engine은 A11 Bionic(2017)부터 Apple SoC에 통합된 NPU입니다. M3/M4 칩의 ANE는 16코어 구성으로 최대 18 TOPS(INT8) 성능을 제공합니다.

Apple Neural Engine 세대별 비교
ANE 코어성능 (TOPS)지원 데이터 타입용도
A11 Bionic2코어0.6FP16얼굴 인식
A14 / M116코어11FP16/INT8Core ML 추론
A16 / M216코어17FP16/INT8실시간 이미지/비디오 처리
A17 Pro / M316코어35FP16/INT8온디바이스 LLM
M416코어38FP16/INT8Apple Intelligence
리눅스 지원 현황: Apple Neural Engine은 macOS/iOS의 Core ML 프레임워크를 통해서만 접근 가능합니다. Asahi Linux 프로젝트에서 Apple Silicon의 GPU 드라이버(AGX)는 리버스 엔지니어링 중이지만, ANE 드라이버는 아직 개발되지 않았습니다. ANE의 레지스터 맵과 펌웨어 프로토콜이 완전히 비공개이기 때문입니다.

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 하드웨어 특성 비교
NPU타겟피크 성능메모리TDP리눅스 메인라인
Intel NPU (Lunar Lake)클라이언트 PC48 TOPS시스템 DDR5 공유SoC 통합O (ivpu)
Intel Gaudi3데이터센터1835 TFLOPS (BF16)128GB HBM2E600WO (habanalabs)
AMD XDNA2클라이언트 PC50 TOPS시스템 DDR5 공유SoC 통합O (amdxdna)
Qualcomm HTP v75모바일/PC75 TOPS시스템 LPDDR5 공유SoC 통합부분 (fastrpc)
Google TPU v5p클라우드459 TFLOPS (BF16)95GB HBM2e~250WX (자체 스택)
Apple ANE (M4)클라이언트/모바일38 TOPS통합 메모리SoC 통합X (비공개)
Samsung Exynos NPU모바일34.7 TOPS시스템 LPDDR5 공유SoC 통합X (vendor)
Huawei Ascend 910B데이터센터4096 TFLOPS (FP16)64GB HBM2e400WX (비공개)

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 아키텍처와 진화

Qualcomm AI Engine 계층 구조 애플리케이션 / ONNX 모델 TensorFlow Lite / PyTorch Mobile / ONNX Runtime QNN SDK (Qualcomm Neural Network SDK) 모델 변환 · INT4/INT8 양자화 · 백엔드 선택 (HTP / GPU / CPU) FastRPC drivers/misc/fastrpc.c SMEM · RPM · rpmsg 버스 remoteproc qcom_q6v5_mss.c DSP 펌웨어 로딩 · 생명주기 HTP (Hexagon Tensor Processor) Hardware Vector 유닛 · Tensor 슬라이스 · Scalar Core · VTCM(Vector TCM) Hexagon DSP → Hexagon v65 HVX → HTP v73 (Snapdragon 8 Gen 2) → HTP v75 (Snapdragon 8 Gen 3, X Elite) 성능: 10 TOPS (8 Gen 1) → 45 TOPS (8 Gen 3) → 75 TOPS (Snapdragon X Elite)

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
오픈소스 현황 (2024~2025):
  • 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

Ascend DaVinci Core 구조 3D Cube (행렬 연산 유닛) 16×16×16 FP16 MAC Array 단일 클럭: 4096 FP16 연산 INT8 모드: 8192 연산/클럭 GEMM·Conv·Pooling 하드웨어 가속 Ascend 910B: 4096 TFLOPS (FP16) Vector Unit 활성화 함수 (ReLU, GELU, Softmax) 원소별 연산, 배치 정규화 Scalar Unit 흐름 제어 · 인덱스 계산 · 동기화 L0/L1 온칩 SRAM 버퍼 L0A (행렬 A) / L0B (행렬 B) / L0C (출력) / L1 (입력 스테이징) HBM2e (Ascend 910B: 64GB, 2 TB/s) 가중치 · 활성화 · 중간 텐서 저장 HCCS (Huawei Cache Coherence System) 칩간 통신 (Atlas 서버: 최대 8 Ascend 910B, NVLink 유사)
Ascend 시리즈 비교
모델용도성능메모리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 생태계 비교

Ascend/CANN vs NVIDIA/CUDA 생태계 대응 관계
계층NVIDIAHuawei Ascend
하드웨어A100 / H100 / H200Ascend 910B / 310P
커널 드라이버nvidia.ko (비공개)ascend_drv.ko (비공개)
런타임 APICUDA RuntimeAscendCL
수학 라이브러리cuBLAS / cuDNNCANN NN Library
컴파일러nvcc (CUDA C++)ATC / TBE Compiler
프레임워크 통합PyTorch CUDAPyTorch torch_npu
자체 프레임워크MindSpore (오픈소스)
칩간 통신NVLink / NVSwitchHCCS
모니터링nvidia-sminpu-smi
메인라인 지원X (nouveau만 일부)X (일부 HiSilicon 가속기만)

CANN 런타임 구조

CANN(Compute Architecture for Neural Networks)은 Ascend 칩을 위한 소프트웨어 스택입니다. PyTorch/TensorFlow와 Ascend 하드웨어 사이를 중재하며, 커널 드라이버와 런타임 라이브러리로 구성됩니다.

PyTorch (torch_npu) / TensorFlow-NPU 프레임워크 플러그인 ← User Space AscendCL (Ascend Computing Language) 유저 공간 API Graph Engine (GE) + TBE 커널 라이브러리 그래프 컴파일러 Kernel Space ↓ Ascend 커널 드라이버 (ascend_drv.ko) 커널 모듈 (비공개) Ascend 910B / 310P 하드웨어
# 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)

커널 드라이버 현황

드라이버 공개 범위 (2025 기준):
  • 비공개: 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)
};
DVFS (Dynamic Voltage and Frequency Scaling): NPU 워크로드 강도에 따라 동적으로 클럭 주파수와 전압을 조절합니다. 가벼운 추론(이미지 분류)은 낮은 OPP(Operating Performance Point)로, 대규모 LLM 추론은 최고 OPP로 실행하여 전력 효율을 극대화합니다.

NPU와 DMA-BUF: 디바이스 간 버퍼 공유

DMA-BUF를 통해 NPU, GPU, 카메라(ISP), 비디오 디코더 간에 제로카피 버퍼 공유가 가능합니다. 이는 카메라 → NPU → GPU → 디스플레이 같은 ML 파이프라인에서 중요한 역할을 합니다.

Camera ISP 프레임 캡처 DMA-BUF NPU 객체 검출 추론 DMA-BUF GPU 바운딩 박스 오버레이 DMA-BUF Display (KMS) 화면 출력 공유 물리 메모리 (DMA-BUF) CPU 복사 없이 디바이스 간 직접 접근 (제로카피)
/* 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_fencedma_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에 임포트 가능
참고: CPU-GPU-NPU 간 메모리를 동일한 가상 주소(Virtual Address)로 공유하고 자동 마이그레이션하는 HMM(Heterogeneous Memory Management)은 HMM (이기종 메모리 관리(Memory Management)) 페이지에서 자세히 다룹니다.

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 활성화
배포판별 NPU 지원 현황 (2025 기준):
  • 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 디버깅 도구

도구용도사용법
dmesgNPU 드라이버 로그dmesg | grep -i "ivpu\|npu\|accel\|xdna"
lspci -vvvNPU PCI 디바이스 정보lspci -d ::0x1200 -vvv (Processing Accel class)
ftraceDRM/Accel 내부 추적echo drm:* > /sys/kernel/debug/tracing/set_event
perf드라이버 성능 분석perf record -a -g -e drm:* -- sleep 5
Intel NPU PluginOpenVINO 통합 프로파일링OpenVINO benchmark_app + NPU 플러그인
xrt-smiAMD XDNA 모니터링Xilinx Runtime CLI (xrt-smi examine)

NPU 유저 공간 소프트웨어 스택

NPU 드라이버는 커널에서 HW 접근과 메모리 관리만 담당하고, 실제 ML 모델 컴파일/실행은 유저 공간 스택이 처리합니다.

계층Intel NPUAMD XDNAQualcomm 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/)
NPU 추론 실행 예시 (OpenVINO + Intel NPU):
# 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 컴파일 파이프라인

NPU 컴파일 파이프라인 ML 모델 ONNX / TFLite PyTorch / JAX 프론트엔드 그래프 가져오기 Op 융합/분할 중간 표현(IR) MLIR / TVM Relay XLA HLO 최적화 타일링 / 양자화 메모리 할당 계획 코드 생성 NPU 바이너리 커맨드 버퍼 UMD 런타임 (사용자 공간) GEM BO 할당 → 커맨드 버퍼 기록 → ioctl(SUBMIT) Kernel Space KMD (drm_sched → HW 디스패치) NPU 하드웨어 실행 주요 컴파일러 스택 MLIR — 다중 수준 IR (LLVM 프로젝트). linalg/tensor/memref 방언 TVM — Apache 오픈소스. Relay IR → NPU 코드 생성 XLA — Google. TF/JAX 그래프 → HLO → NPU 백엔드 VPUX Compiler — Intel NPU 전용 (MLIR 기반) Vitis AI — AMD XDNA 전용 (Xilinx 계열) CANN TBE — Huawei Ascend 전용 (TVM 파생) 커맨드 버퍼 = 타일링된 연산 + DMA 전송 + 동기화 명령

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와 연결됩니다.

NNAPI 스택 구성
계층구성 요소역할
TFLite / ML Kit모델 로드, 추론 요청
프레임워크NNAPI RuntimeOp 지원 확인, 벤더 드라이버 선택
HALVendor NNAPI HAL모델 컴파일, 버퍼 관리
유저 드라이버UMD (Vendor)ioctl 호출, 커맨드 제출
커널NPU KMDGEM 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 토폴로지 인식

멀티 NPU 토폴로지 유형
토폴로지구성대역폭사례
PCIe 직접 연결CPU ↔ NPU (PCIe switch 경유)PCIe Gen5: ~64 GB/sIntel Gaudi, AMD MI300
전용 인터커넥트NPU ↔ NPU (전용 링크)100~400 GB/sGaudi RoCE, HCCS
SoC 내부 버스NPU ↔ CPU (AXI/NoC)수십 GB/sIntel 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";
};
NPU 전원 상태 전이
상태전력 소비복귀 시간설명
D0 (Active)100% TDP즉시잡 실행 중
D0 Idle~30% TDP마이크로초클럭 게이팅(Clock Gating), 잡 대기
D3 Hot~5% TDP밀리초전원 도메인 OFF, 컨텍스트 보존
D3 Cold~0%수십 밀리초완전 전원 차단, FW 재로딩 필요

NPU 프로파일링과 성능 분석

커널 트레이스포인트(Tracepoint)

NPU 드라이버는 커널 트레이스포인트를 통해 잡 제출, 실행, 완료 이벤트를 추적할 수 있습니다. ftraceperf로 수집하여 성능 병목을 분석합니다.

/* 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

하드웨어 성능 카운터

NPU 공통 하드웨어 성능 카운터
카운터설명최적화 지침
MAC UtilizationMAC 어레이 활용률 (%)<70%: 타일링/패딩(Padding) 비효율. 컴파일러 최적화 필요
DMA BandwidthDDR↔SRAM 전송 대역폭피크 대비 <50%: 더블 버퍼링 미적용 또는 텐서 레이아웃 문제
SRAM Hit RateSRAM 재사용률<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 전송과 캐시 플러시 오버헤드가 크게 줄어듭니다.

CXL NPU 연결의 이점
특성기존 PCIe NPUCXL NPU
메모리 접근DMA 전송 (명시적)load/store (투명)
캐시 일관성SW 관리 (flush/invalidate)HW 자동 (CXL.cache)
지연 시간마이크로초 (DMA 설정 포함)나노초 (load/store)
메모리 풀링불가 (디바이스별 분리)가능 (CXL.mem 공유 풀)
커널 영향DMA API + IOMMUmm 서브시스템 + DAX + CXL 드라이버

칩렛(Chiplet) 기반 NPU 설계

칩렛 기반 설계는 NPU 타일을 독립적인 다이(Die)로 제조한 뒤 패키징 수준에서 결합하는 방식입니다. AMD XDNA가 이미 AI Engine 타일을 칩렛 형태로 통합하고 있으며, Intel도 Foveros/EMIB 패키징으로 NPU 칩렛을 SoC에 통합하고 있습니다.

NPU 보안 고려사항

NPU를 멀티테넌트(Multi-Tenant) 환경에서 운영할 때 보안이 중요합니다. 다음은 커널 드라이버 수준에서 고려해야 할 보안 요소들입니다.

NPU 보안 메커니즘
위협대응 메커니즘구현 위치
메모리 격리 위반 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

새로운 기술 동향

NPU 미래 기술 동향
기술설명커널 영향
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 캐시 관리, 메모리 압박
리눅스 커널 NPU 로드맵: DRM Accel 서브시스템은 계속 발전하고 있습니다. 주요 변화 방향은 다음과 같습니다:
  • 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 서브시스템 API
  • include/drm/gpu_scheduler.h — drm_sched API
  • include/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 소개 및 설계 원칙
외부 참고 링크:
NPU 드라이버 개발 시 주의사항:
  • 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 드라이버를 개발할 때 참고할 모범 사례를 정리합니다.

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  — 에러 주입 + 복구 확인

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