文章目录
- 前言
- 一、能力模型
- 二、知识地图
- 1.底座:NPU抽象硬件架构、Host/Firmware分工与OS底层机制
- 2.正确性底座:C++并发与内存
- 3.Runtime子系统
- 4.两个闭环
- 三、学习路径
- 阶段0:建立硬件直觉与Host/Firmware分工意识
- 阶段1:打通UMD/KMD/Firmware 与命令下发路径
- 阶段2:补齐C++正确性底座
- 阶段3:掌握Runtime核心子系统设计
- 阶段4:对接上层框架与并发隔离
- 阶段5:建立Debug/Profile的分层排查能力
- 四、高优先级
- 五、学习方法
- 总结
前言
本文主要记录我在求职 NPU / TPU / GPGPU Runtime 岗位过程中的学习路径与思考。Runtime 的本质,是在用户态与硬件之间搭建一条正确、低延迟、可并发、可观测的指令与数据通路。这条通路决定了几件事:一个算子能否被正确下发,一个计算任务能否被高效调度,一个模型能否在多流多卡环境下稳定并发执行。
因此,学习 Runtime 不能只停留在 OS 与 C++ 的基础概念上,更要建立软硬件协同的工程视角。这条通路从三个方向勾勒出学习路径:
向下,需要理解 NPU 的抽象硬件架构——计算单元、存储层级、搬运单元——以及 UMD/KMD 边界、命令队列与内存映射。在有Firmware的架构中,这条通路并不是“用户态直达硬件”的两段式,而是 Host OS(KMD)与 Device Firmware 分工协作的三段式:KMD 掌握资源管理权与系统交互入口,Firmware 掌握设备内部硬件行为的最终控制权。
向上,需要承接 PyTorch、vLLM、推理引擎以及 Triton/Tilelang DSL 对 kernel 下发、整图执行、多流同步和资源隔离的真实需求。
中间,需要用 C++ 并发与内存模型保证多线程提交、队列生产和事件同步的正确性。
每学习一个知识点,都要能回答三个问题:
机制是什么:它在硬件、Firmware、驱动、runtime 中处于什么位置?与相邻层通过什么接口交互?
为什么这样设计:它解决了什么约束和取舍?换一种设计会付出什么代价?
出问题时怎么排查:超时、挂死、性能不达预期时,如何沿着 Host 调度层、命令队列层、Firmware处理层、Device 执行层、端到端尾延迟逐层定位?
这三个问题贯穿全文,只有把 Runtime 的核心内容——设备管理、指令下发、kernel 调度、任务队列、整图下发、多模型并发、资源隔离、内存分配器、多流/事件同步、异常处理——都落到“机制、取舍、排查”三个维度上,知识体系才能真正收拢,而不是停留在名词清单。
一、能力模型
| 层级 | 核心能力 | 检验标准 |
|---|---|---|
| 硬件事实层 | 理解NPU计算单元(标量/矢量/矩阵)、存储层级(片外 HBM/GM → 片内 L1/L0/UB)、搬运单元(DMA引擎)、片内/片间互联 | 能画出典型算子的数据通路并指出瓶颈层级 |
| Firmware对端层 | 理解KMD与Firmware的指责切分、邮箱/共享内存通信、设备侧状态机 | 能判断故障发生在HOST提交侧还是Device处理侧 |
| 驱动边界层 | 理解UMD/KMD、mmap、ioctl、Ring Buffer+Doorbell、IOMMU | 能讲清一次任务从用户态到硬件队列的完整路径 |
| 正确性底座 | C++并发、内存序、无锁队列、RAII资源管理 | 能写出无数据竞争、内存序正确的提交队列 |
| Runtime子系统 | Context/Stream/Event、命令队列与指令下发、整图下沉、内存分配、多流、异常 | 能设计最小Runtime原型并说明取舍 |
| 上层与观测 | PyTorch/vLLM/Triton接口需求、profile分层排查 | 能定位host-bound、device-bound、同步与争用问题 |
二、知识地图
整个体系可以用“一个底座,一条正确性主线,一组Runtime子系统,两个闭环” 概括。
1.底座:NPU抽象硬件架构、Host/Firmware分工与OS底层机制
- NPU抽象硬件架构:计算单元、存储层级、搬运单元;理解每一级存储与执行流水线的绑定关系;
- 最终目标:给定一个算子的数据流,能估算出它在哪一级存储上会成为带宽瓶颈,以及 runtime 应该把 tiling 参数和 DMA 描述符设计成什么样来隐藏搬运延迟。
- Host OS与Firmware的职责切分:
| 机制 | AP CPU侧(Host OS/KMD) | NPU侧(Firmware/设备 RTOS) |
|---|---|---|
| 内存 | 内存分配策略、SMMU页表配置、dma-buf导入导出 | 片上存储分区、本地buffer管理、执行硬件配置 |
| 中断 | 接受IRQ、执行ISR、更新任务状态、唤醒上层 | 在任务完成或出错时产生中断/邮箱时间 |
| 调度 | 作业级排队、依赖管理、优先级、超时控制 | 作业内硬件编排、上下文切换、微秒级任务派发 |
| 通信 | 通过BAR/MMIO写Doorbell、读状态寄存器 | 通过邮箱或共享内存与KMD交换命令与状态 |
关键认知:KMD 决定“把哪箱货物装上哪辆卡车”,Firmware 决定“货物在卡车上怎么摆放、按什么顺序卸货”。 实时性要求极高的微秒级任务切换和上下文保存恢复,通常由 Firmware 在其 RTOS 或裸机循环中完成,以规避 Host OS 调度延迟的不确定性。
UMD/KMD 分层与用户态直接下发:KMD 负责 PCIe 枚举、BAR 映射、中断、大页物理内存等特权操作;UMD 通过 mmap 映射寄存器与命令队列,实现用户态直接下发。
Ring Buffer + Doorbell:用户态构造命令包,写入环状队列,内存屏障后写 Doorbell 通知硬件。注意:Doorbell 通知的对象在有 Firmware 的架构中往往是 Firmware 而非裸硬件,Firmware 再解析描述符并配置硬件。
ioctl 边界:设备打开、内存分配/mmap/free、job 提交、中断处理与状态更新。
IOMMU/SMMU:IOVA 到物理地址翻译,决定设备可见地址与隔离设计。
DMA 与缓存一致性:Coherent DMA 与 Streaming DMA 的区别,invalidate/flush 的时机,PCIe P2P DMA 的数据通路影响。
邮箱(Mailbox)机制:特权邮箱与用户邮箱的分工、命令与响应的同步、以及 Firmware 状态查询。
2.正确性底座:C++并发与内存
- 内存模型与内存序:relaxed、acquire/release、seq_cst在多核与设备可见性场景下的实际差异。
- 无锁数据结构:SPSC队列、ABA问题、内存回收、伪共享对ring buffer性能的影响。
- 现代C++资源管理:RAII、智能指针边界、hot path 避免不变要分配;Device/Context/Stream/Event句柄的封装与析构同步。
3.Runtime子系统
- 资源模型:Device、Context、Stream、Event/Notify的归属、隔离与生命周期。
- 命令队列与指令下发:队列满/空、批量提交、doorbell聚合、多线程提交原子性;命令描述符格式本质是Host KMD与Device Firmware之间的契约。
- 整图下沉:Capture/Replay模式,固化task序列,消除逐算子下发开销;动态shape与动态tiling参数刷新。
- 内存分配器:Device Memory Pool、片上buffer pool、按生命周期服用、HBM碎片控制。
- 多流与Event同步:流内保序、流间依赖、计算与搬运重叠、死锁排查。
- 多模型并发与资源隔离:计算/HBM/片上buffer争用、Stream优先级、QoS/虚拟通道优先级、抢占粒度。
- Exception Handling and Timeouts:任务超时、设备挂死、可恢复/不可恢复、abort与复位边界;关键区分故障在Host提交侧、Frimware处理侧还是硬件执行侧。
4.两个闭环
- 向上接口闭环:PyTorch
torch.compile与图后端、vLLM的Triton kernel路由与fallback、Triton/Tilelang DSL对launch配置和参数buffer的接口需求。 - 可观测闭环:按Host调度层、命令队列层、Firmware处理层、Device执行层、端到端延迟分层建立排查框架。
三、学习路径
阶段0:建立硬件直觉与Host/Firmware分工意识
- 目标:能画出NPU的典型数据通路,并说清一次算子执行中数据如何从HBM搬到片上、由哪个计算单元消费、哪一级可能成为瓶颈;同时能画出Host KMD与Device Firmware的写作流程图。
- 重点:
- 计算单元、存储层级、DMA引擎、片内/片间互联;
- 理解“存储层级与执行流水线强绑定”;
- 理解KMD与Firmware在内存、中断、调度、通信四个维度的职责边界;
- 理解邮箱机制与Doorbell通知的“对端”是谁;
- 输出物:
- 一页纸的NPU数据通路图,标志DMA路径、片上buffer和潜在瓶颈点;
- 一张Host KMD<–>Firmware协作时序图,覆盖任务提交、执行、完成中断、状态更新。
阶段1:打通UMD/KMD/Firmware 与命令下发路径
- 目标:能完整讲清一次任务从用户态API调用到硬件队列消费、再到Firmware解析执行的完整路径。
- 重点:
- mmap映射寄存器空间与命令队列;
- Ring Buffer生产/消费模型;
- Doorbell触发前的内存屏障;
- ioctl的职责边界;
- IOMMU地址翻译与设备可见地址;
- Coherent/Streaming DMA与缓存维护;
- 邮箱/共享内存通信机制,以及Firmware如何解释命令描述符并配置硬件;
- 命令格式作为Host-Firmware 契约的设计含义;
- 输出物:一个用户态SPSC ring buffer模拟提交队列,包含内存屏障、水位检查和批量提交逻辑;附一份“命令描述符字段说明”,标注每个字段由Host填写、由Firmware解释还是由硬件消费。
阶段2:补齐C++正确性底座
- 目标:能在多线程、弱内存一致性硬件假设下写出正确的runtime提交代码。
- 重点:
std::memory_order的实际语义、无锁SPSC队列、伪共享、ABA与内存回收、RAII句柄管理、hot path分配控制。
- 输出物:
- 一个带压测的无锁SPSC队列,并记录不同内存序下的性能与正确性差异。
阶段3:掌握Runtime核心子系统设计
- 目标:能把岗位职责中的每个子系统当作有约束的工程问题来讨论,而不背API。
- 重点:
- Context/Stream/Event资源模型与隔离;
- 命令队列与多线程提交;
- Capture/Replay整图下沉及动态参数刷新;
- 多级内存分配器与碎片控制;
- 多流同步、计算搬运重叠、死锁排查;
- 多模型并发下的优先级、QoS与抢占粒度;
- 超时、abort、设备挂死与恢复边界;
- 故障定位的“对端分界”:Host提交失败、Firmware无法处理、硬件执行异常,三者的区分方法和各自的排查手段。
- 输出物:
- 一个最小runtime原型设计文档,至少覆盖Device/Context/Stream/Event、任务队列、内存池和超时处理;附一张“任务挂死排查决策树”,标注Host侧与Firmware侧的分界检查点。
阶段4:对接上层框架与并发隔离
- 目标:理解runtime接口形态由上层框架的使用方式决定。
- 重点:
- PyTorch与图后端、Graph Capture的嵌入方式;
- vLLM中Triton kernel路由、补丁机制与fallback;
- Triton/Tilelang DSL对grid/block launch、参数buffer、动态tiling的接口需求;
- 多模型并发下的资源争用、优先级反转和隔离手段。
- 输出物:
- 一份“上层框架对runtime接口需求清单”,按kernel launch、内存、stream、同步、错误处理分类。
阶段5:建立Debug/Profile的分层排查能力
- 目标:面对“NPU利用率低”这类问题,能逐层排查而不是直接跳进kernel优化。
- 排查框架:
| 层次 | 核心问题 | 典型手段 |
|---|---|---|
| Host调度层 | 是否host-bound | 下发间隔、capture/replay命中率、框架开销 |
| 命令队列层 | 队列是否满、doorbell是否及时 | Ringbuffer水位、提交批大小 |
| Firmware处理层 | Firmware是否取走命令、状态机是否卡住、邮箱是否响应 | 邮箱往返时间、Firmware状态寄存器、命令超时计数 |
| Device执行层 | kernel是否高效、搬运是否重叠 | kernel耗时、DMA与计算重叠率、片上buffer命中 |
| 端到端 | 多流/多模型尾延迟 | 流间同步等待、资源争用、优先级反转 |
- 输出物:
- 一棵可执行的性能问题排查树,每个分支给出验证方法和可能结论;每个分支明确标注“这是Host还是Firmware侧的问题”。
四、高优先级
- UMD/KMD边界+Ring Buffer/Doorbell+内存序;
- Host/Firmware职责切分+邮箱通信+命令描述符契约:用于在生产环境中定位跨侧故障;
- DMA/IOMMU+缓存一致性+零拷贝:决定能否正确设计数据通路;
- C++无锁并发+内存模型:决定队列和同步代码是否正确;
- Context/Stream/Event模型+整图下沉;
- 内存分配器+多流并发+异常超时;
- vLLM/Triton接口侧+profile方法论:体现上下游生态理解和调优闭环;
五、学习方法
建议每个阶段都产出可验证的东西:
- 一张图:数据通路图/协作时序图;
- 一个原型:ringbuffer 或者最小runtime设计;
- 一次压测:不同内存序、不同批提交策略下的性能对比额;
- 一棵排查树:从host-bound到firmware-stuck到device-bound的定位路径;
- 一份接口清单:上层框架对runtime的真实需求;
- 一份契约文档:命令描述符的字段定义与两侧职责标注。
每学习一个机制,额外写一个“失败场景”:如果这个机制缺失或误用,会出现什么现象、如何定位、问题可能落在Host侧还是Firmware侧。
总结
NPU Runtime 的学习之路,可以概括为一条闭环:
硬件事实 → Host/Firmware 分工 → 驱动边界 → C++ 正确性 → Runtime 子系统 → 上层接口 → 可观测与调优 → 回到硬件事实验证。
从“构造一条从用户态到 NPU 的最小任务通路”开始。先把 Ring Buffer、Doorbell、内存屏障、邮箱通信和一次任务下发打通,再逐步扩展内存池、多流、整图下沉、多模型并发和 profile 能力。每扩展一个模块,都回到三个问题:机制是什么、为什么这样设计、出问题怎么排查;并且多问一句:这个问题在 Host 侧还是 Firmware 侧?