Linux GPU/NPU加速器总体架构:完整软件栈

第2章:GPU/NPU完整软件栈——从Application、Runtime、UMD、KMD到Firmware和Hardware

上一章解释了CPU、GPU、NPU为何形成不同的执行、调度和内存模型。本章沿一项任务的生命周期继续向下:一个API调用怎样变成设备命令,怎样穿过用户态与内核态,怎样被固件和硬件执行,完成结果又怎样返回应用。

1
2
3
Application → Framework/API → Runtime/UMD
→ ioctl/mmap/poll
→ KMD → Firmware → Hardware

这不是所有设备都必须严格实现的七层协议,而是一张责任地图。真实软件可能合并相邻层,也可能增加编译服务、守护进程或多个固件处理器。

0. 源码基线与边界

1
2
3
4
repository: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
tag: v7.3-rc6
commit: a90ee4305c4a5df72c11b31dacfdc76e00fcf78a
checked: 2026-10-09

主要坐标:

1
2
3
4
5
6
7
drivers/gpu/drm/
drivers/gpu/drm/scheduler/
drivers/accel/
drivers/accel/amdxdna/
include/uapi/drm/
Documentation/gpu/drm-uapi.rst
Documentation/accel/

本章分析稳定的软件边界,不把某个厂商的私有协议描述成Linux通用接口。

2.1 为什么GPU/NPU软件栈会这么深

加速器同时涉及高级API、编译、命令生成、内存虚拟化、多进程隔离、调度、电源、故障恢复和异步完成。把所有职责塞入一层,会让更新速度、安全边界和硬件控制相互冲突。

1
2
3
变化快、API相关、可失败隔离 → 更适合用户态
特权、全局仲裁、安全隔离 → 必须由内核控制
低层状态机、实时设备控制 → 常下沉到Firmware/Hardware

2.2 Application到底负责什么

Application表达业务目标:绘制场景、执行Compute Kernel、运行神经网络。它管理业务数据和算法流程,但不应了解Doorbell寄存器、GPU页表格式或NPU Tile配置。

2.3 Framework和Programming API是什么

Framework提供Tensor、Graph、Scene等高级对象;Vulkan、OpenGL、OpenCL等Programming API定义可移植的设备操作语义。

1
2
3
Application:我要完成什么业务
Framework:用什么高级计算/图形模型表达
API:以什么标准接口请求加速能力

2.4 Runtime到底是什么

Runtime把编程模型落到设备使用流程,可能负责设备枚举、队列、内存API、同步、后端选择和调用编译器。它更靠近Programming Model,而KMD更靠近系统与硬件控制。

2.5 Runtime为什么不能等同于Driver

Runtime和UMD边界不是统一标准。某些项目中Runtime调用独立UMD;另一些库同时承担两种角色。判断时应看责任而非文件名:是否实现高级API语义、是否生成设备命令、是否通过内核UAPI访问设备。

2.6 UMD是什么

UMD(User Mode Driver)是用户态中了解具体GPU/NPU命令格式和能力的软件。典型GPU路径:

1
Vulkan Application → Mesa RADV → DRM ioctl → AMDGPU KMD

RADV是UMD,AMDGPU是KMD。

2.7 为什么需要User Mode Driver

Shader编译、Pipeline状态和命令生成变化快、复杂且与API强相关。放在用户态可以独立升级、隔离崩溃、使用普通开发工具,并避免扩大内核攻击面。

2.8 UMD通常负责哪些事情

1
2
3
4
5
6
7
API Translation
Shader/Kernel Compile
Pipeline与Descriptor状态
Command Buffer生成
资源状态跟踪
Queue与依赖准备
Submit参数构造

UMD不能因此绕过安全:KMD仍控制设备文件、可访问对象、地址空间和特权动作。

2.9 Shader、Kernel和Graph编译在哪里发生

GPU通常在用户态把GLSL/SPIR-V等编译为设备ISA;NPU编译器通常在用户态完成Graph Fusion、量化、Tiling、内存规划和Codegen。KMD不适合承担持续演进的高级编译优化。

2.10 Command Buffer通常在哪里构建

现代GPU常由UMD在用户映射的Buffer中批量构建Command Buffer,再一次提交给KMD。这样减少系统调用并把API状态翻译留在用户态。

1
UMD生成命令 ≠ 用户可任意控制硬件

KMD仍通过对象句柄、VM映射、提交规则和硬件隔离限制其能力;不同硬件的验证强度也不同。

2.11 KMD是什么

KMD(Kernel Mode Driver)运行于内核,连接Linux设备模型、DRM/accel公共框架和具体硬件。它是用户请求进入共享设备的特权边界。

2.12 为什么KMD必须放在内核态

IOMMU配置、中断、电源域、设备复位、特权寄存器和跨进程资源仲裁不能交给任意进程,否则一个Client即可破坏其他Client或整个系统。

2.13 KMD通常负责哪些事情

1
2
3
4
5
6
7
8
9
Probe与Device Lifetime
File/Client私有状态
Context和Address Space
Buffer Object与GPUVA/IOVA
Submission与Scheduler接口
Fence、IRQ和Completion
Power Management
Fault、Hang、Reset与Recovery
Firmware装载和通信

2.14 Device Ownership

多个进程打开同一设备时,内核用Device FD及其file-private对象确定请求主体,再把Context、Buffer、VM和Queue关联到该Client。关闭FD触发生命周期清理,但被共享或仍在执行的对象可能由引用计数延后销毁。

2.15 Process与Context Isolation

1
2
Process A → Context/VM A → Buffer A
Process B → Context/VM B → Buffer B

隔离依赖KMD、GPU MMU/IOMMU、PASID和固件/硬件权限共同实现。Context ID本身不是安全边界,必须与地址翻译和访问检查结合。

2.16 Memory Management职责如何分层

1
2
3
4
5
Application       请求逻辑资源
Runtime/UMD 管理API Resource与放置偏好
KMD 创建BO、记账、固定/迁移、建立映射
GPUVM/IOMMU 形成设备地址空间
Hardware MMU 执行实时地址翻译与权限检查

一个Buffer可能同时有应用对象、UMD对象、KMD BO、CPU VA、GPUVA、IOVA和物理Backing,不能把它们混成一个“地址”。

2.17 Command Submission职责如何分层

1
2
3
4
5
6
API Call
→ UMD Build Command Buffer
→ ioctl提交对象句柄、地址和依赖
→ KMD检查Client状态并建立Job
→ Scheduler选择可运行Job
→ Firmware/HW Queue消费

“Validate”具体做什么高度依赖硬件和UAPI,不能假设KMD会逐条解析所有现代GPU命令。

2.18 Firmware为什么出现

现代设备拥有大量Queue、Context、Engine、电源状态和错误状态机。Firmware能够屏蔽硬件代际差异,并在更靠近设备的位置完成低延迟控制。

2.19 Firmware和KMD是什么关系

KMD负责OS侧资源与安全边界,通过Mailbox、共享Ring、Doorbell等协议向Firmware发请求;Firmware验证其协议范围内的消息、编程设备内部资源并报告完成或错误。

1
KMD与Firmware是协议协作关系,不是普通函数调用关系。

2.20 Firmware Scheduler是什么

Firmware Scheduler可能管理Firmware可见Context、逻辑Queue、硬件Queue Slot和设备内部优先级。它掌握的状态比KMD更接近硬件,但通常不了解完整的Linux进程策略。

2.21 Hardware Scheduler是什么

Hardware Scheduler直接在Engine、CU/SM、Wave/Warp或Tile等资源上选择执行单元。它的时间尺度更短,使用硬件状态做决策,Linux无法逐个调度这些最细粒度对象。

2.22 三层Scheduler的区别

层 常见对象 主要视角
Kernel Scheduler Job、Entity、Context Client公平、优先级、依赖、超时
Firmware Scheduler FW Queue、Context、HW Slot 设备内部资源、抢占、电源/故障
Hardware Scheduler Wave/Warp、Workgroup、Tile 即时就绪状态和执行资源

同名不等于同一个算法或调用链。

2.23 UMD和KMD之间通过什么通信

1
2
3
4
5
open:获得Device FD
ioctl:控制与提交
mmap:映射Buffer、Ring或Doorbell
poll/read:等待事件或可读状态
shared memory:批量交换队列数据

FD提供身份和生命周期锚点,系统调用提供权限检查与内核入口。

2.24 ioctl到底解决什么问题

设备需要CREATE_CONTEXT、CREATE_BO、VM_BIND、SUBMIT、WAIT、QUERY等非通用文件操作。调用链:

1
2
3
4
5
6
ioctl(fd, cmd, arg)
→ syscall
→ DRM/accel dispatcher
→ driver handler
→ copy/validate参数
→ 执行或排队

UAPI一旦发布必须长期兼容,因此结构大小、保留字段、错误码和并发语义都很重要。

2.25 mmap为什么重要

mmap让用户态直接访问受控的虚拟映射,可用于Buffer、Command Ring、Instruction Buffer或Doorbell,避免频繁复制和系统调用。

它不等于暴露物理地址:KMD的mmap回调、VMA、页表、访问属性和对象生命周期仍控制映射。

2.26 poll、wait和event如何用于异步完成

设备执行期间CPU可继续工作。完成后KMD signal Fence、更新共享状态或排队Event,并唤醒poll/wait等待者。忙轮询只适用于极短且明确受控的低延迟场景。

2.27 DRM Device Node是什么

DRM为用户态导出字符设备节点,常见:

1
2
/dev/dri/card0
/dev/dri/renderD128

打开后,DRM Core创建drm_file等file-private状态,驱动在其上关联Client对象。

2.28 Primary Node和Render Node是什么

Primary Node保留显示、DRM Master和部分特权/历史接口;Render Node只开放标记为可安全用于渲染的接口,不提供Modesetting和全局特权控制。

2.29 为什么Render Node对GPU计算很重要

离屏渲染和GPGPU不应依赖显示服务器认证。Render Node通过文件权限提供非特权GPU入口,并减少全局名称和显示资源带来的攻击面。主线要求驱动显式把驱动私有ioctl标记为Render允许。

2.30 NPU的/dev/accel模型

Linux accel Core复用DRM基础设施,但以独立设备类别和/dev/accel/accel*节点隔离纯计算加速器,避免图形用户栈误识别。其基本模式仍是FD + ioctl + mmap/poll。

2.31 一次GPU任务从Application到Hardware

1
2
3
4
5
6
Application → Vulkan/OpenCL
→ Runtime/UMD编译Shader并构建Command Buffer
→ Render Node ioctl
→ KMD关联Context、BO、GPUVM和Fence
→ Kernel/Firmware Queue
→ Engine → Workgroup/Wave/Lane

2.32 一次NPU任务从Application到Hardware

1
2
3
4
5
Application → AI Framework → Runtime/Compiler
→ 已编译Program + Buffer + Resource需求
→ accel ioctl → NPU KMD Context
→ Resource Solver/Firmware
→ Partition/Tile → DMA + Compute

2.33 一次Memory Allocation如何跨越多层

1
2
3
4
5
6
7
Application申请Tensor/Image
→ Runtime创建Resource
→ UMD请求BO
→ KMD选择System RAM/VRAM等Backing
→ mmap提供CPU访问(如需要)
→ VM_BIND/映射建立GPUVA或IOVA
→ Device MMU访问

分配、放置、映射和同步是不同动作,成功分配物理Backing不代表设备地址已经可用。

2.34 一次Command Submission如何跨越多层

提交包含命令Buffer、引用对象、依赖和输出同步点。KMD解析UAPI、固定生命周期、建立依赖并排队;Scheduler/Firmware决定何时进入硬件。提交成功只表示工作被接受,不表示已经完成。

2.35 一次Completion如何反向返回Userspace

1
2
3
4
5
6
7
Hardware Done
→ IRQ或Firmware Event
→ KMD确认对应Job
→ 更新状态并signal dma_fence
→ 唤醒poll/wait或signal Syncobj
→ UMD回收资源
→ Application观察完成

错误完成也必须传播,避免应用永久等待。

2.36 谁负责Policy,谁负责Mechanism

1
2
3
4
5
选择哪个Job先运行:Policy
把Job写入Ring:Mechanism

决定Workload占几列:Policy
配置Partition寄存器:Mechanism

Policy可能分散在Runtime、KMD和Firmware;分析代码时要找到真正拥有全局信息和最终决定权的层。

2.37 为什么不是所有逻辑都放进Kernel

编译器、Pipeline优化和Graph变换体积大、变化快、容易处理不可信输入。放入内核会扩大攻击面、增加崩溃影响,并被内核UAPI兼容承诺束缚。

2.38 为什么也不能把所有逻辑都放Userspace

用户态不能安全独占全局设备、编程IOMMU、处理中断、控制电源或复位其他Client共享的硬件。公平、隔离和恢复要求可信KMD参与。

2.39 Firmware-first架构为什么越来越常见

Firmware-first把Queue、Context、功耗和硬件状态机更多地下沉到设备控制器,便于支持复杂硬件与低延迟决策。代价是协议兼容、可观测性和调试难度上升;KMD日志可能只能看到“请求已发出”,真正阻塞发生在Firmware内部。

2.40 真实案例:Mesa、RADV和AMDGPU

1
2
3
4
5
6
Vulkan App
→ Vulkan Loader
→ Mesa RADV(UMD:Shader/Pipeline/Command)
→ libdrm或直接DRM ioctl
→ AMDGPU(KMD:BO/VM/Context/Submit/IRQ/Power/Reset)
→ GPU Firmware/Hardware

具体提交接口随版本和硬件演进,但UMD/KMD责任边界长期稳定。

2.41 真实案例:AMD XDNA NPU

主线文档描述的概念路径:

1
2
3
4
5
Runtime/UMD → amdxdna KMD
→ 每Workload Context的User Channel/Mailbox
→ Firmware中的ERT
→ Resource Solver决定空间/时间映射
→ XDNA Partition/Tile

MERT服务特权管理请求,ERT服务Workload Context;DMA在Host DDR与Memory Tile间搬运数据。这是KMD—Firmware—Hardware分工的现实样本。

2.42 Linux内核视角下的软件栈边界

内核看到的不是Vulkan Draw或神经网络Conv语义,而是:

1
2
3
Device + File/Client + Context
Buffer + Mapping + Address Space
Job/Queue + Fence/Event

因此后续源码阅读应从open/ioctl/mmap/poll入口追踪这些对象的创建、所有权、状态变化和销毁。

2.43 本章实验

2.43.1 观察设备节点

1
2
ls -l /dev/dri/ /dev/accel/ 2>/dev/null
lsof /dev/dri/* 2>/dev/null

2.43.2 用strace观察边界

1
2
strace -f -e openat,ioctl,mmap,poll,ppoll \
<gpu-or-npu-program>

预测:大量复杂用户态逻辑最终收敛为少量Linux系统调用。

2.43.3 查找DRM ioctl分发

1
rg "DRM_IOCTL_DEF|DRM_RENDER_ALLOW" drivers/gpu/drm include/uapi/drm

2.43.4 观察XDNA分层

1
2
rg "mailbox|ERT|MERT|workload|partition" \
Documentation/accel/amdxdna drivers/accel/amdxdna

实验结论应写成“观察→对象→所在层→下一层接口”,而不是只记录命令输出。

2.44 性能与工程意义

端到端延迟可分解为:

1
2
3
4
5
6
7
8
Application/Framework
+ Compile/Runtime
+ UMD Command Build
+ syscall/KMD
+ Queue Wait
+ Firmware Delay
+ Hardware Execute
+ Completion/Wakeup

GPU Busy低可能是提交批次太小、依赖等待或编译卡顿;NPU利用率低可能是Tiling、DMA、DDR或Partition策略问题。性能定位的第一步是Layer Classification,而不是先改内核。

2.45 常见误区

误区1:Runtime就是Driver

边界可能重叠,但职责不同:Runtime实现编程模型,UMD理解具体设备,KMD控制系统资源。

误区2:UMD只是薄封装

现代UMD可能包含庞大的编译器、状态跟踪和命令生成逻辑。

误区3:KMD负责Shader或Graph编译

通常错误,这类工作主要在用户态。

误区4:KMD只是写寄存器

现代KMD还管理VM、BO、Context、Scheduler、Sync、IRQ、Power和Recovery。

误区5:Firmware只在启动时运行

很多现代设备Firmware长期参与命令、调度、功耗和故障处理。

误区6:三个Scheduler是同一个Scheduler

它们运行层次和调度对象都不同。

误区7:mmap就是暴露物理地址

错误,它仍是KMD控制的虚拟映射。

误区8:ioctl返回成功代表任务完成

通常只代表提交被接受;真正完成由Fence/Event表达。

2.46 博客总结

现代GPU/NPU软件栈深,是因为高级编程模型、编译、系统安全、设备控制和硬件执行必须采用不同更新速度与信任边界:

1
2
3
4
5
6
7
Application描述目标
Framework/API提供编程模型
Runtime/UMD完成编译、状态管理和命令生成
KMD负责特权资源、隔离、内存、提交和恢复
Firmware管理设备内部Context、Queue和状态机
Hardware执行命令
Completion沿Fence/Event反向返回

官方阅读入口:

2.47 学完检查题

  1. Application、Framework、Runtime和UMD分别解决什么问题?
  2. 为什么Runtime与UMD不是永远严格分开的两个库?
  3. Shader/Graph编译为什么主要位于用户态?
  4. UMD构建Command Buffer后,KMD还必须保证什么?
  5. 哪些能力决定KMD不能被完全移到用户态?
  6. Device FD如何帮助建立Client身份和生命周期?
  7. BO、CPU VA、GPUVA、IOVA和物理Backing有何区别?
  8. 提交成功为什么不等于执行完成?
  9. Kernel、Firmware、Hardware Scheduler分别管理什么?
  10. Firmware-first的收益和调试代价是什么?
  11. Primary Node与Render Node的权限模型有何区别?
  12. accel Core为什么复用DRM又使用独立设备节点?
  13. 一次Completion怎样从硬件传播到Userspace?
  14. RADV和AMDGPU分别处于哪一层?
  15. AMD XDNA中Mailbox、ERT、Partition和Tile是什么关系?
  16. 如何用strace证明UMD/KMD边界真实存在?
  17. GPU Busy低时,为什么不应首先断定硬件算力不足?
  18. 请画出Memory、Submit、Completion三条跨层路径。

下一章将进一步拆解同一个Memory、Context和Scheduler功能在Runtime、UMD、KMD与Firmware中分别保存什么状态、执行什么Policy和提供什么Mechanism。


Linux GPU/NPU加速器总体架构:完整软件栈
https://cj0510.github.io/2026/10/09/Linux Kernel/GPU/Linux-GPU-NPU加速器总体架构-第2章-完整软件栈/
作者
CJ1018
发布于
2026年10月9日
许可协议
CC BY-NC-SA 4.0