gregordinary/rockchip-npu-notes
GitHub: gregordinary/rockchip-npu-notes
一份基于真机逆向工程的 RK3588 NPU 硬件参考笔记,记录寄存器命令接口行为及各子系统细节,供开源推理栈开发者使用。
Stars: 4 | Forks: 1
# Rockchip NPU — 逆向工程笔记
## AI 声明
rockchip-npu-notes 中的文档由 AI 撰写,主要是 Claude Code (Opus 4.8)。这些文档是作为一系列副项目的一部分而生成的,并在此发布,因为它们可能对其他人的工作有用。不保证信息的准确性。
## 关于 rockchip-npu-notes
这些是按子系统组织、与项目无关的关于 Rockchip RK3588
NPU 的笔记(由主线 `rocket` DRM-accel 驱动驱动)。
它们是在真实设备(Turing
RK1, 32 GB, 主线 kernel ~7.1)上对硬件进行逆向工程,同时在 `rocket` 之上构建 FOSS 推理栈时建立的:一个用户空间 matmul 库、一个 ggml backend、一个 NPU 时钟补丁,以及一个
TFLite delegate。不过,我们学到的大部分内容并不特定于这些项目中的任何一个——这是关于芯片及其寄存器命令
接口行为的事实。这就是这里的内容。
如果你试图通过 `rocket`(或
任何原始 regcmd 路径)在 RK3588 NPU 上运行你自己的计算,这个仓库提供了关于精度编码、原生 tile 布局、整数输出 `size_e` quirk、DPU eltwise 单元可以和不能累加什么、CBUF operand-reuse 位、会导致你的第一个任务挂起的 MRDMA 陷阱、每个 fd 的 IOVA 窗口,以及以 1/5 速度启动的时钟的观察、见解和细节。
有关将驱动程序库、前端和
kernel 补丁结合在一起的从头到尾的演练,请参阅[指南](guide/)。
## 如何阅读本文
每个主张都标记了它是如何被确立的:
- **[HW sweep]** — 通过在真实的 NPU 上扫描某个值/几何结构,并观察 bit-exact 与 garbage 与 nothing-written 的结果,经验性地进行逆向工程。最强有力的证据是:这就是硬件的实际行为。
- **[source-confirmed]** — 通过阅读权威的 FOSS 源代码得到证实:Mesa
`rocket`/Teflon 驱动程序、开放的 NVDLA 文档,或者我们基于其构建的 RK3588 寄存器头文件(`npu_hw.h`,来自 Jasbir Matharu 的 `rk3588-npu`)。参见 [SOURCES.md](SOURCES.md)。
当某个事实经过硬件确认 *并且* 与某个来源匹配时,会同时出现这两个标签。负面结果(即不起作用的事物)与正面结果一样被仔细记录下来——因为它们是学习成本最高的。
## 地图
| 文档 | 子系统 | 事实 |
|---|---|---|
| [hardware-overview.md](hardware-overview.md) | 整个 NPU | NVDLA 谱系,3 核心,CBUF 12×32 KB,精度菜单,TOPS 与现实 |
| [datatypes.md](datatypes.md) | 整个 NPU | 数据类型能力矩阵 — 每个 dtype 的精度字段、输出类型、MAC 速率及用途 |
| [matmul-as-conv.md](matmul-as-conv.md) | CNA/CORE/DPU | matmul 如何作为 1×1 卷积运行;tiling;数据流;对齐规则 + 特征高度 <4(M==1 GEMV)中断 |
| [depthwise-conv.md](depthwise-conv.md) | CNA/CORE/DPU | depthwise 与直接卷积的区别:`CONV_MODE=3`+`DW_EN`, `weights_kernels=1`, `size_e=3`, surfaces ×2 |
| [encodings/conv-transpose.md](encodings/conv-transpose.md) | CNA (lowering) | ConvTranspose2d/deconv 没有硬件模式 — 它 lowers 到 dilate-input + `rot180(Wᵀ)` + 一个 stride-1 前向卷积;180° 翻转推导 + `pad ≤ d·(K−1)` 约束 |
| [encodings/resize-upsample.md](encodings/resize-upsample.md) | CNA (lowering) | nearest/bilinear resize = 一个带有 box/triangle kernel 的 depthwise transposed conv;triangle 的 stride-subsample 是单位划分 ⇒ half-pixel 2-tap bilinear;`C%32` |
| [encodings/precision-field.md](encodings/precision-field.md) | CNA/CORE/DPU | 所有 6 种 dtype 的 3-bit 精度值(包括 int4=6 RE 和 bf16=3 / tf32=7 float rungs) |
| [encodings/tile-layouts.md](encodings/tile-layouts.md) | CNA / DPU-RDMA | 特征 cube C2,权重布局,输出 cubes — 每个 dtype |
| [encodings/cross-op-chaining.md](encodings/cross-op-chaining.md) | CNA / DPU (cube) | 一个 fp16 matmul 缩窄的输出 cube **就是**下一个 op 的输入特征 cube(两者的 `feat_idx` C2=8) → 将一个 op 的输出 BO 直接馈入下一个,无需主机 de-tile/re-tile (bit-exact [HW]);仅限 fp16(int/bf16 输出 cubes 不匹配);multi-tile 需要一个 KACC full-cube 输出 + 匹配的 `Nt==Kt`;**在 transform-bound encoder 上产生开销(~80% transform/compute),在 compute-bound LLM prefill 上开销约 2–3%** |
| [encodings/size-e-quirk.md](encodings/size-e-quirk.md) | DPU (write-out) | 整数输出以 `size_e=7` 为 stride,无论字节宽度如何 |
| [encodings/output-transpose-int16.md](encodings/output-transpose-int16.md) | DPU (write-out) | 为什么 int16 没有原生 matmul 输出;字节分解路径 |
| [encodings/k-accumulation.md](encodings/k-accumulation.md) | DPU-EW / DPU-RDMA | fp16 K-accum 有效;int8/int16/int32 EW K-accum 在硬件上不可用 (HW-dead) |
| [encodings/sdp-stage-precision.md](encodings/sdp-stage-precision.md) | DPU SDP (BS/BN/EW) + CORE | 3 个 SDP 阶段的精度:没有阶段添加按元素的 *integer* tensor(BS/BN 按通道 int32 广播,EW 是按元素的但 ALU 仅限 float) → 设备上的 int32 K-accum 是不可能的 = int8 上限;CACC 没有跨 op 累加 |
| [encodings/dpu-lut-activation.md](encodings/dpu-lut-activation.md) | DPU LUT (SDP) | 片上 NPU 激活(NVDLA LE/LO 混合):sigmoid/hardsigmoid/tanh/SiLU + **GELU(精确的 2-pass `x·Φ(x)`;单次 pass 在平坦尾部出现尖峰)** + conv→act 融合 + **LeakyReLU** + **sqrt/rsqrt/reciprocal/EXP/LOG**(移位的单表,在 ~128× 范围内 <1%;EXP 可独立运行;**LOG = 第一种 SIGNED-output 正域类型,通过 OUT_CVT 偏移实现负 `out_lo`,使用绝对误差度量**)+ 完全在 NPU 上的 EW **mul/add/sub/div**;x≈0 的多路复用故障 = LE/LO mux 根据 `sign(x)` 进行选择;**QUIRK 1:表内平坦/饱和运行会错误触发 mux(~128 尖峰)⇒ 单 pass LUT 融合仅限曲线区域,使用 2-pass `x·gate(x)`**;**QUIRK 3:恰好达到最大宽度(列 8191)会破坏约 54 个 cube 位置**;**QUIRK 4:一个 q=0 的 LUT 表条目被错误解码为垃圾值 ~4.0 ⇒ 将每个表条目向下取整为 q≥1** |
| [encodings/whisper-encoder.md](encodings/whisper-encoder.md) | composition | 完全在 NPU 上的 Whisper/transformer encoder block (cos=1.000000):EXP LUT,按行 softmax(主机 row-max,NPU 上没有 max-reduce 数据路径)+ **LogSoftmax**(`x−logsumexp`,主机的 `log(s)` 类似 softmax 的 `1/s`,按行 `ew_sub`)+ 稳定的 **cross-entropy**(`logsumexp − logits[target]`,NPU 上的 logsumexp + 一个 **主机 GATHER** — 不存在 HW gather;由于 loss 跳过了 fp16 输出存储,达到了 fp32 级别),LayerNorm(在一个堆叠行的 feature-reduce 中完成两次 reduction),conv1d(以 HEIGHT 轴上的 TIME 进行 lower — IH=1 会溢出 feature banks),multi-head self-attention(将 key 数量填充到 %32 + 掩码 pad score 列;matmul 拒绝未对齐的 N/K),2-pass GELU,完整的 pre-norm block |
| [encodings/siglip-encoder.md](encodings/siglip-encoder.md) | composition | 端到端在 NPU 上的 **SigLIP-B/16 vision encoder**(SmolVLM-256M 前端) = patch-embed (im2col→matmul, stride==kernel patchify) + pos + 12×`rocket_encoder_block_fp16` `(L=1024,d=768,12h,d_ff=3072)` + post-LN;**相对于 fp32 HF oracle 的保真度为 0.999998 cosine(SHARD 为 0.95)**;热启动下延迟约 2.71 s(常驻:预打包的 GEMMs、多核 head-fanned attention、主机 softmax + bit-exact GELU LUT;开启所有优化手段后约 2.30 s,1.78×),与 SHARD 的 2.24 s 相比并非 iso-hardware;**NPU 事实:full-attention softmax 在 NPU 上受数据移动限制(~6.5 s,batching heads 无济于事) ⇒ 一旦 scores 被 de-tiled,主机多线程 softmax 的成本约低 10×**;剩余底线 = matmul de-tile + 主机-softmax scores 的往返 |
| [encodings/feature-reduce.md](encodings/feature-reduce.md) | CNA/CORE/DPU (matmul) | 在隐藏/特征轴上进行 reduce (`sum_h x[m,h]`) = 一个 **ones-vector matmul** — PPU **不能**对通道轴进行 reduce(它仅在通道内对空间 `[H,W]` 进行池化);fp32-accumulate,transformer-norm / softmax 收缩。**Cumsum / prefix sum** = 相同的 matmul,只是 ones-COLUMN 被扩展为一个 **triangular ones MATRIX** (`out=in·Lᵀ`; incl/excl×fwd/rev; HW bit-exact) ⇒ reduce-as-matmul 系列 = full + weighted reduce + prefix scan |
| [encodings/rmsnorm-onnpu.md](encodings/rmsnorm-onnpu.md) | composition | RMSNorm = square→feature-reduce→(host rsqrt)→scale;rsqrt 保留在 HOST 上(M 个标量按行;否则属于 LUT 域);fp16 平方溢出需要 2 的幂的预缩放;按行广播缩放原语 |
| [encodings/ffn-block.md](encodings/ffn-block.md) | composition | gated-MLP FFN (GeGLU/SwiGLU):与 matmul 相比唯一的新 op 是 `act(gate)⊙up`;已通过 cosine 验证;resident-cube 融合计划(当今由主机切换) |
| [encodings/norm-vision.md](encodings/norm-vision.md) | composition | 视觉 norms(BatchNorm/GroupNorm/InstanceNorm/L2-Normalize)= 使用不同的 `[N,C,P]` reduce-axis 分组的 LayerNorm 机制;(batch,group) 块是连续的 ⇒ feature-reduce reshape 只是一个纯视图;`G=C`→InstanceNorm,`G=1`→LayerNorm-over-CHW,BatchNorm = 无 reduce 的按通道 affine;没有新的 regcmd |
| [encodings/ppu-pooling.md](encodings/ppu-pooling.md) | PPU (PDP) | 片上 NPU MaxPool / AveragePool:PPU+PPU_RDMA 程序,avg `RECIP=fp16(65536/k)`,enable mask 0x60 |
| [encodings/ppu-reduce-mean.md](encodings/ppu-reduce-mean.md) | PPU (PDP) | 通过伸缩式多遍(kernel cap 16)在 SPATIAL [H,W] 轴上执行 GlobalAvgPool / Mean;一个 PPU 写入的 sub-4 中间值会被下一个链式 pass 错误读取;**GlobalMax/MinPool (ReduceMax/Min) 重用相同的引擎 — 幂等 ⇒ 无需倒数,贯穿整个链 BIT-ACT**(关于正交的通道轴 reduce,参见 feature-reduce.md) |
| [encodings/out-cvt-converter.md](encodings/out-cvt-converter.md) | DPU (write-out) | 输出转换器 `(acc×SCALE)>>SHIFT` — 整数 scale/shift;fp32-cast + integer-scale fold,分数 dequant 无法实现 |
| [encodings/regcmd-task-model.md](encodings/regcmd-task-model.md) | PC / tasks | task = 完整的 regcmd + enable;delta regcmd 不会触发;寄存器文件在所有任务间全局持久化 |
| [encodings/cbuf-reuse.md](encodings/cbuf-reuse.md) | CNA (CBUF) | WEIGHT_REUSE / DATA_REUSE operand-reuse 位 |
| [encodings/cbuf-bank-slack.md](encodings/cbuf-bank-slack.md) | CNA (CBUF) | int8 feature DMA 会多读取一个 bank — 预留 `data_bank = fd_banks+1` |
| [encodings/mrdma-trap.md](encodings/mrdma-trap.md) | DPU-RDMA | 你必须发出的 regcmd block,否则任务会超时 |
| [perf/not-mac-bound.md](perf/not-mac-bound.md) | 整个 NPU | 在该工作点下,与 dtype 无关的 ~460 GOP/s 底线 — 量化在此不会加速 matmul(瓶颈条件,而非硅片定律) |
| [perf/quant-prefill-microbatch.md](perf/quant-prefill-microbatch.md) | LLM prefill | 量化后的 GGUF prefill **受限于每个微批次的 dequant** — `-ub 2048` 使其速度提升约 2×,量化 *类型* 与吞吐量无关,量化 ≈ 0.64× F16;短小的量化 prefill 路由到 CPU (`ROCKET_MIN_M_QUANT`);F16 NPU prefill 的优势 **随模型大小而扩展**(0.8B 1.44× → 9B 3.65× CPU);包含 hybrid-DeltaNet 的 Qwen3.5/3.6 已验证 |
| [perf/iova-and-multicore.md](perf/iova-and-multicore.md) | kernel/DMA | 每个 fd 4 GB IOVA 窗口;N 个核心使用 N 个 fd |
| [perf/clock.md](perf/clock.md) | clock/PM | 200 → 600 MHz,冷启动 power-domain 陷阱,900 MHz 硬锁 |
| [perf/bo-sync-cost.md](perf/bo-sync-cost.md) | kernel/DMA | `PREP_BO`/`FINI_BO` 缓存同步与 BO 大小成正比 — 合理调整重复同步的 KACC 输出 BO 的大小(常驻 fp16 下约 +11%) |
| [perf/ppu-pooling-not-detile.md](perf/ppu-pooling-not-detile.md) | PPU | PPU 是一个 POOLING 引擎 (PDP),而不是 RUBIK — 不能进行 de-tile;在任一方向上都没有片上布局转换 |
| [perf/sram-nbuf.md](perf/sram-nbuf.md) | system SRAM / IOMMU | NPU 通过类似于 DDR 的 IOVA 访问 SRAM(没有 NPU↔SRAM 总线 / 没有“NBUF”引擎);主线 rocket 不支持 SRAM;syssram 由编解码器拥有;作用微弱的手段(gather-bound 的回读) |
| [perf/attention-offload-crossover.md](perf/attention-offload-crossover.md) | prefill attention | 卸载 `FLASH_ATTN_EXT` **从 ~2K 开始产生优势**,带有 per-worker 的 QK/AV 提交链(≤1K 达到持平,@8K 达到 1.45×;没有链的情况下约 6K 交叉点) — CPU attention 是超线性的,一旦多核化 + 链式化,NPU attention 便趋于平缓;基于 `n_kv`(≈ 滑动窗口长度)而非 `n_tokens` 来控制卸载 |
## 一段话总结
RK3588 NPU 是一个派生自 NVDLA 的 3 核加速器。`rocket` kernel 驱动程序
是一个通用的寄存器命令提交器(`CREATE_BO` / `SUBMIT` / `PREP_BO`),因此你
可以通过发出 Mesa 用于
卷积的相同 CNA→CORE→DPU regcmd 来自己驱动 matmul — matmul 只是一个 1×1 卷积。它原生支持 int4 / int8 /
int16 / fp16 / bf16 / tf32(+ int32 / fp32 输出);我们为
每一项都有一个可用的 matmul(int16 是唯一的例外 — 它没有原生的 matmul *输出*,因此它是通过 int8 字节分解完成的)。权重和激活必须由
主机预先分散到原生 tiled 布局中(NPU 没有片上 row-major→tiled
转换)。整数输出写入的 stride 有一个 quirk(`size_e=7`)。你
可以通过 DPU eltwise 单元在片上累加 fp16 K-partials,但不能累加整数
的 — DPU eltwise ALU 仅支持浮点。int8 feature cube 有一个 CBUF 陷阱 — 它的
DMA 会多读取一个 bank,所以你必须给它 `data_bank = fd_banks+1` 的余量
(fp16 不受影响)。matmul 的行是卷积的空间高度,而低于
4 的高度(`M==1` 单向量 / GEMV 情况)在硬件上会对每种
dtype 计算错误 — 因此 `M%4==0` 是真正的约束,软件将 `M==1` 填充到 4。你
通过打开 3 个或以上的 file descriptor 来控制这 3 个核心(每个 fd 对应一个调度实体)。DPU 还有一个
NVDLA LUT 单元,可在片上计算非线性激活(sigmoid/tanh/SiLU/GELU/sqrt/rsqrt/
reciprocal/exp) — 这足以与 matmul 结合,在 NPU 上运行完整的 transformer/Whisper encoder
block;两个 LUT 陷阱:一个恰好为 `q=0` 的表条目会错误解码为垃圾值 ~4.0
(将条目向下取整为 `q≥1`),并且恰好达到 13-bit 最大 cube 宽度会破坏尾部(在其下进行 tile)。而最重要的性能事实是:在当前的工作点,matmul 受
DMA/dispatch 限制,而不是受 MAC 限制 — 因此量化为你换来的是 RAM,而不是 prefill 速度
(受瓶颈条件限制,而非永久的硅片定律)。时钟以 200 MHz 启动,并且
只有在 power domain 启动后才能
从驱动程序内部提高。
## License
本仓库中的文档 — 包括散文笔记、表格和编码说明 — 采用
[Creative Commons Attribution 4.0 International](LICENSE) (CC-BY-4.0) 许可:在标明出处的情况下可自由
重用。
`ppu-rknn-capture/` 下的辅助脚本带有其自己的 `SPDX-License-Identifier:
GPL-3.0-or-later` 头文件,并据此进行许可。第三方捕获保留其上游
版权和许可条款 — 特别是 `ppu-rknn-capture/registers.xml`(来自 Mesa
`rocket`/Teflon 驱动程序),已在 [SOURCES.md](SOURCES.md) 中致谢。
标签:AI加速器, NPU, RK3588, 云资产清单, 硬件研究, 网络安全监控, 逆向工程, 驱动开发