Linux GPU/NPU加速器总体架构:CPU、GPU、NPU的本质区别
第1章:CPU、GPU、NPU到底有什么本质区别——执行模型、调度模型和内存模型
后面学习Linux GPU/NPU驱动时,我们会连续遇到context、job、queue、ring、firmware、GPUVM、IOMMU、partition、tile和fence。
如果直接把它们套进CPU调度器的task_struct → runqueue → CPU模型,很容易得到错误结论。例如:把一个GPU Job理解成一个Linux线程,把GPU Scheduler理解成CFS,或者认为CPU与集成GPU共享DDR就不再需要GPU虚拟内存。
本章先建立整个专题的坐标系:
1 | |
0. 源码基线与阅读边界
本文使用截至2026年10月8日的最新Linux主线发布候选版本:
1 | |
主要源码与文档坐标:
1 | |
这一章讲稳定的架构概念,并用主线中的DRM、accel和AMD XDNA作为落点;不会假设所有GPU都采用同一种硬件命名,也不会把某一家NPU实现推广为统一标准。
1.1 为什么要先比较CPU、GPU、NPU
CPU调度主链容易形成直觉:
1 | |
但加速器驱动管理的对象通常更粗:
1 | |
CPU、GPU、NPU并不是简单的“快慢差别”。硬件为了不同目标做出的取舍,决定了Linux应该在什么粒度管理它们。
1.2 不要先从“算力”理解CPU/GPU/NPU
常见概括是:
1 | |
对入门有用,但对源码不够。更适合系统软件的表述是:
| 处理器 | 典型首要目标 | 典型手段 |
|---|---|---|
| CPU | 单线程延迟与复杂控制 | 分支预测、乱序执行、大缓存、推测执行 |
| GPU | 大规模数据并行吞吐 | SIMT、大量Lane、大量驻留Wave、宽内存带宽 |
| NPU | 张量/Dataflow的吞吐与能效 | 矩阵单元、Tile、显式本地存储、DMA、编译期规划 |
“典型”非常重要:现代CPU有向量单元,GPU有专用矩阵单元,NPU也可能包含可编程核心,三者边界会交叉。但设计重心仍然不同。
1.3 CPU的核心设计目标
CPU希望尽快推进一条充满依赖、分支、异常和系统调用的指令流:
1 | |
CPU愿意为此投入大量晶体管实现预测和乱序结构。它追求的不是同时放入最多算术单元,而是让一条通用指令流尽量少停顿。
1.4 CPU的执行模型
从Linux视角看:
1 | |
线程的可恢复执行状态至少包含:
1 | |
调度器保存prev并恢复next,CPU随后直接继续执行next的指令流。
1.5 Process、Thread、task_struct和Core
Linux使用task_struct统一表示可调度任务。用户口中的进程和线程都对应task;区别主要在于它们是否共享mm_struct、文件表、信号处理等资源。
1 | |
CPU调度器的对象是task,而不是整个地址空间。一个多线程进程的不同线程可以同时运行在不同CPU Core上。
1.6 CPU为什么适合复杂控制流
典型高性能CPU包含:
1 | |
遇到分支时先预测,遇到独立指令时乱序执行,发生异常时恢复精确架构状态。这些机制昂贵,却能降低单线程可见延迟。
1.7 CPU为什么重度依赖Cache
1 | |
DRAM延迟远高于CPU周期。Cache利用时间与空间局部性,把常用数据留在离执行单元更近的层级。CPU一般让硬件自动决定命中、替换和回填,软件只提供布局与预取提示。
因此可以把CPU内存层次概括为:
1 | |
1.8 CPU调度的本质:时间复用
一个Core不能在同一时刻执行多个普通task:
1 | |
Linux CPU调度主要解决:
1 | |
这是一种以时间复用为核心的调度。
1.9 GPU为什么会出现
假设要处理一百万个像素:
1 | |
若各元素执行近似相同的运算,继续堆叠复杂CPU控制逻辑并不经济。GPU把更多面积用于算术Lane、寄存器和带宽,通过大量并行工作换取总体吞吐。
1.10 GPU的核心设计目标
GPU典型目标:
1 | |
一句话:
GPU通常不追求让每个逻辑线程最快,而追求单位时间完成尽可能多的工作。
1.11 GPU的执行模型
概念层级:
1 | |
这里的GPU Compute Kernel是设备程序,不是Linux Kernel。厂商术语不同,但“粗粒度Dispatch逐层展开为大量硬件执行Lane”是稳定概念。
1.12 Kernel、Grid、Workgroup、Warp、Wave和Lane
| 层级 | 含义 |
|---|---|
| Kernel/Shader | 在GPU上运行的程序 |
| Grid/Dispatch | 一次完整并行工作域 |
| Workgroup/Block | 可协作、共享局部资源的一组work-item |
| Warp/Wave | 硬件以相近指令步调执行的一组Lane |
| Lane/Work-item | 最细的逻辑数据元素执行实例 |
真正需要建立的边界是:
1 | |
1.13 GPU Thread为什么不等于Linux Thread
Linux Thread:
1 | |
GPU Thread/Lane:
1 | |
若给百万GPU Lane各创建一个task_struct,管理成本会完全不可接受,而且CPU调度器也不了解GPU寄存器、共享内存和SIMT执行掩码。
1.14 SIMT到底解决什么问题
SIMT(Single Instruction, Multiple Threads)让一组逻辑线程以相同指令流处理不同数据:
1 | |
每个Lane仍有自己的寄存器数据与逻辑身份,但控制和取指可以共享。若Lane走不同分支,就会出现branch divergence:硬件通常需要使用执行掩码分批推进不同路径,吞吐下降。
1.15 GPU为什么需要大量硬件并发
GPU访问显存也会等待。它隐藏延迟的重要方法不是只依靠超大Cache,而是保留大量可运行Wave:
1 | |
这里的切换主要发生在GPU内部,不是Linux schedule()。大量驻留上下文要求很大的寄存器文件,程序的寄存器/共享内存占用也会反过来限制occupancy。
1.16 GPU调度到底有几层
“GPU Scheduler”至少可能指:
1 | |
看到scheduler时第一问必须是:
它调度的对象是什么,运行在哪一层,控制哪种资源?
1.17 GPU Context是什么
GPU Context可理解为某个Client在设备上的一组执行环境:
1 | |
具体字段由驱动决定。一个Process可以拥有多个Context,用于不同Engine、优先级或隔离域;因此Process != GPU Context。
1.18 GPU Job是什么
Job是KMD能够跟踪、排序、提交并等待完成的一份工作。它可能代表:
1 | |
Job仍然比GPU Thread高很多层:
1 | |
Linux主线DRM调度器用struct drm_sched_job表达公共调度对象,但厂商驱动通常把它嵌入自己的Job结构。
1.19 GPU Queue是什么
1 | |
Queue至少要区分三个层级:
| 队列 | 维护者 | 作用 |
|---|---|---|
| Software Queue | UMD/KMD | 保存尚未送入设备的工作 |
| Firmware Queue/Ring | 驱动与固件 | 主机和固件交换命令 |
| Hardware Queue | 设备 | 硬件真正消费命令的槽位/队列 |
现代硬件还可能允许User Queue直接写共享Ring,再由固件完成验证和映射。
1.20 GPU Engine、SM和CU是什么
GPU可能包含多类Engine:
1 | |
SM、CU或WGP则是计算Engine内部承载Wave/Lane的计算资源。KMD通常选择Job送往哪个Engine或硬件Ring,不会为每个Warp指定某个SM。
1.21 GPU Memory Model
CPU和GPU都需要地址翻译,但对象可能不同:
1 | |
Linux主线DRM包含GEM/TTM、GPUVM、DRM Buddy、Sync Object等公共基础设施。它们分别解决Buffer对象、放置/迁移、GPU虚拟地址范围和同步等问题。
1.22 GPU VA与CPU VA的区别
同一个Buffer可能同时具有:
1 | |
这些地址不能随意互换。传统模型中CPU和GPU使用独立页表;SVA/SVM、HMM等机制可以增强地址空间协同,但也不意味着所有硬件天然共享完全相同的翻译和一致性语义。
1.23 VRAM与System RAM
离散GPU常见:
1 | |
集成GPU常见:
1 | |
共享物理DRAM仍要处理:
1 | |
因此“UMA”不等于“没有GPU内存管理”。
1.24 GPU为什么也需要Page Table和MMU
如果用户直接把物理地址写入命令:
1 | |
GPU MMU和GPUVA提供隔离、重定位、稀疏映射与故障报告。主线DRM GPUVM公共代码抽象gpu_vm、gpu_vma和对象到VM的关联,但页表格式和更新机制仍由驱动实现。
1.25 NPU为什么会出现
神经网络中大量工作由规律的矩阵乘、卷积、激活和张量变换构成。此类工作常受数据搬运和乘加能耗支配。针对访问模式、数值格式和数据流定制硬件,能够获得更好的Performance/Watt。
NPU不是一个严格统一的微架构名称,而是一类面向AI/张量工作负载的加速器。
1.26 NPU和GPU到底有什么不同
典型对比:
1 | |
GPU也可以有Tensor Core,NPU也可以有VLIW/向量处理器,因此差异是“设计中心”而非绝对边界。源码分析时应以具体设备文档为准。
1.27 NPU的执行模型
1 | |
NPU通常不暴露“每个神经元一个Linux线程”。KMD管理的是已编译程序、Buffer、Workload和设备资源绑定。
1.28 Graph、Workload、Program和Command
1 | |
一个Workload可能携带输入输出Buffer、指令Buffer、Scratchpad需求、DMA描述符、Tile数量和依赖Fence。不同驱动命名会不同,必须回到UAPI和具体结构确认边界。
1.29 Tile、Compute Engine和Matrix Engine
NPU常把计算与本地存储组织成二维或分组Tile:
1 | |
Tile可能包含向量/VLIW核心、本地Program/Data Memory、DMA端口和片上互连。Matrix Engine则更偏向执行MAC/矩阵指令。两者不是所有NPU都采用的统一术语。
1.30 Systolic Array的基本思想
Systolic Array让数据在处理单元之间有规律地流动并复用:
1 | |
目标是减少反复访问外部DRAM,让权重、激活或部分和在阵列内部被多次使用。算术峰值只有在数据持续供给且布局匹配时才有意义。
1.31 Scratchpad为什么重要
Cache由硬件自动决定替换,Scratchpad/Local SRAM通常由编译器、Runtime或固件显式规划:
1 | |
神经网络访问模式较可预测,显式管理能换取更确定的延迟、更高复用率和更低能耗;代价是编译器和Runtime必须正确完成Tiling与容量规划。
1.32 NPU为什么大量依赖DMA
在NPU上,数据搬运往往与计算同等重要:
1 | |
性能瓶颈可能来自DDR带宽、DMA描述符、NoC拥塞、双缓冲失效或Scratchpad复用不足,而不是MAC单元数量。只看TOPS无法解释真实应用性能。
1.33 NPU的Spatial Scheduling
空间调度把不同物理区域同时分配给不同Workload:
1 | |
调度器不仅决定“谁先运行”,还决定“占多少列、哪些Tile、本地存储与DMA资源”。资源分配与调度策略因此紧密耦合。
Linux主线AMD XDNA文档给出了现实例子:二维Compute/Memory Tile Array可在列边界形成隔离Partition,并绑定Workload Context。
1.34 NPU的Temporal Scheduling
时间调度让多个Workload轮流使用同一Partition:
1 | |
AMD XDNA同时支持空间和时间共享。驱动中的Resource Solver决定阵列重新分区和Workload映射,固件执行Context到Column的绑定。这是具体实现示例,不应推广为所有NPU的强制架构。
1.35 CPU、GPU、NPU调度模型对比
1 | |
CPU主要分配CPU时间;GPU KMD主要在Client间安排Job/Queue到Engine;NPU还可能同时分配空间资源。
1.36 CPU、GPU、NPU内存模型对比
| 维度 | CPU | GPU | NPU |
|---|---|---|---|
| 主要地址 | CPU VA | GPUVA,可能另有CPU VA | Device VA/IOVA |
| 翻译 | CPU MMU/页表 | GPU MMU/GPU页表 | IOMMU和/或设备MMU |
| 外部存储 | System RAM | VRAM/System RAM | DDR/HBM等 |
| 本地层次 | 多级Cache | Register、Cache、Shared/Local Memory | Scratchpad/Local SRAM |
| 数据移动 | Load/Store + Cache | Load/Store、Copy Engine | 显式DMA通常很关键 |
| 管理侧重点 | 缺页、回收、NUMA | Buffer放置、映射、迁移、共享 | 编译期规划、DMA、Tile本地容量 |
IOVA是IOMMU面向设备的I/O虚拟地址;GPUVA是GPU程序引用的虚拟地址。某些系统两者可能形成连续翻译链,不能只凭数值相同就认为它们是同一层地址。
1.37 CPU、GPU、NPU同步模型对比
CPU常见:
1 | |
异步设备常见:
1 | |
CPU提交工作后会继续运行,设备稍后完成,所以需要一个可跨时间、跨驱动表达“工作何时完成”的对象:
1 | |
Linux主线的struct dma_fence包含context、seqno、回调链和signaled状态。它更接近异步活动完成点,不是简单的“GPU版mutex”。
1.38 Linux内核视角下三者如何统一
Linux不会把每个GPU Lane或NPU PE变成task,而是在系统边界管理更粗对象:
1 | |
CPU task仍负责调用Runtime、提交命令、等待结果和处理异常;设备内部并行由加速器自己的执行机制负责。
1.39 DRM/accel为什么会成为公共基础设施
不同加速器共同面对:
1 | |
Linux accel子系统明确复用DRM代码:accel设备是一类DRM设备,但使用独立设备节点/主设备号,避免图形用户栈误把纯计算加速器当作显示GPU。驱动代码放在drivers/accel/。
这说明DRM不只等于“显示”:它已经积累了大量适用于异步加速器的内存、调度和同步机制。
1.40 从Process到Hardware的三条完整路径
1.40.1 CPU路径
1 | |
1.40.2 GPU路径
1 | |
1.40.3 NPU路径
1 | |
1.41 本章实验
1.41.1 查看主线源码布局
1 | |
预测:DRM目录不仅有显示代码,也有scheduler、TTM/GEM、GPUVM和同步相关公共代码;accel目录包含多种计算加速器驱动。
1.41.2 建立对象坐标表
为每个对象回答五个问题:
1 | |
对象至少包括:task_struct、GPU Context、drm_sched_entity、drm_sched_job、GPUVM、dma_fence和NPU Workload。
1.41.3 只观察DRM Scheduler骨架
1 | |
先画:
1 | |
不要在第一章深入锁和超时恢复。
1.41.4 观察AMD XDNA
1 | |
预测:文档会把二维阵列、Column、Partition、Workload Context、固件和DMA联系起来,验证NPU调度包含空间资源选择。
1.41.5 观察运行中的设备
1 | |
没有/dev/accel不代表内核没有accel框架,可能只是机器没有对应硬件、驱动未启用或尚未绑定。
1.42 性能与工程意义
看到GPU利用率低,问题未必是GPU算力不足:
1 | |
同样,NPU标称TOPS高也不保证应用快:
1 | |
嵌入式Camera → ISP → GPU → NPU → Encoder流水线中,dma-buf共享、IOMMU映射、Fence依赖和DDR带宽往往比单个单元峰值更重要。
1.43 常见误区
误区1:GPU就是很多CPU Core
错误。GPU的执行粒度、控制流处理、调度层次和内存隐藏策略都不同。
误区2:GPU Thread等于Linux Thread
错误。GPU Lane没有task_struct,不由Linux CPU调度器逐个调度。
误区3:Linux GPU Scheduler调度Warp
通常错误。KMD更常调度Job、Entity或Queue;Warp/Wave由硬件层管理。
误区4:NPU就是没有图形功能的GPU
错误。典型NPU更强调Tensor/Dataflow、Scratchpad、DMA和空间资源。
误区5:GPU/NPU只有一个Scheduler
错误。Runtime、KMD、Firmware和Hardware都可能各有调度决策。
误区6:CPU和GPU共享DDR就不需要GPU内存管理
错误。仍需GPUVA、权限、页表/映射、缓存一致性、生命周期和同步。
误区7:Fence就是GPU版mutex
不准确。Fence表达异步活动的完成和依赖;mutex主要保护CPU临界区互斥。
误区8:NPU性能只看TOPS
错误。编译、Tiling、DMA、带宽、本地存储复用和资源分区都可能限制性能。
1.44 博客总结
CPU、GPU和NPU的根本差异来自优化目标与硬件执行模型:
1 | |
它们进一步塑造Linux软件对象:
1 | |
本章最重要的判断方法是:
1 | |
主线Linux 7.3-rc6中的DRM Scheduler、GPUVM、dma_fence、accel Core和AMD XDNA说明:Linux不会把设备内部每个并行单元变成CPU task,而是在KMD边界管理Context、Buffer、Address Space、Job、Queue和完成依赖。
官方源码与文档入口
- Linux 7.3-rc6源码树
- Linux DRM Memory Management
- Linux Compute Accelerators Introduction
- Linux AMD XDNA NPU Documentation
- Linux dma-buf与DMA Fence文档
1.45 学完检查题
- 为什么CPU、GPU、NPU不能只按“算力大小”比较?
- Linux Thread与GPU Lane的状态、调度者分别是谁?
- SIMT与SIMD有什么联系?为什么分支分歧会降低效率?
- GPU通过什么方式隐藏内存访问延迟?
- Runtime、KMD、Firmware和Hardware Scheduler分别可能调度什么?
- GPU Context为什么不等于Linux Process?
- Job、Command Buffer、Workgroup和Wave是什么层次关系?
- Software Queue、Firmware Queue和Hardware Queue如何区分?
- Engine与SM/CU为什么不是同一层对象?
- 同一个Buffer为什么可能同时有CPU VA、GPUVA和IOVA?
- 集成GPU共享DDR后,哪些内存管理问题仍然存在?
- GPU MMU和GPUVM分别解决什么层面的问题?
- NPU的Graph、Program、Workload和Command如何区分?
- Scratchpad与CPU Cache的主要管理方式有何不同?
- 为什么NPU分析必须把DMA和计算放在一起?
- Spatial Scheduling与Temporal Scheduling分别分配什么?
- AMD XDNA的Resource Solver和Firmware各承担什么角色?
dma_fence为什么适合异步设备工作?- accel为何复用DRM基础设施,却使用独立设备类别?
- Camera → GPU → NPU流水线为什么可能受同步和带宽限制,而不是算力限制?
下一章将沿软件栈继续向下:
1 | |
重点回答一次GPU/NPU调用为什么必须跨越这么多层,以及每一层的责任边界。