RKNPU 编程架构
本页介绍 RKNPU 的编程模型:硬件没有指令集,软件提交的是内存里的一段 寄存器命令(regcmd),由 PC 块顺序读出并写入各计算块;驱动在内核侧只做 BO 的 DMA 与栅栏,不做算子编译。硬件结构见 硬件架构。
1. 从应用到硬件的分层
mainline 路线(本页以此为准)[1]:
应用 / 前端 (ggml / TFLite / ONNX Runtime)
│ 调用算子
▼
librocketnpu(用户态驱动 + matmul/算子库)
│ 生成 regcmd,提交 drm_rocket_submit
▼
rocket(内核 DRM accel 驱动)→ 写各块寄存器、DMA BO、收中断
▼
RKNPU 硬件(PC → CNA/CORE/DPU/PPU)
rocket是通用寄存器命令提交器,不锁定某一个算子集。内核只负责把 BO DMA 到设备并按你给的寄存器程序触发各块;"怎么算"完全由用户态决定[1]。- 厂商路线是 BSP 里的
rknpu驱动(设备名不是/dev/accel/accelN);同一个 用户态库可通过提交层适配它[1]。
2. job、task 与 regcmd
一次 drm_rocket_submit(一个 job)携带一个或多个 task,每个 task 是一个
{regcmd IOVA, regcmd_count} 描述符[1]:
struct drm_rocket_task {
__u64 regcmd; /* regcmd 缓冲的 DMA 地址 */
__u32 regcmd_count; /* 命令字数 */
/* ... */
};
regcmd 是一条 NPUOP(op, value, reg) 命令流,以控制尾部结束:
NPUOP(OP_NONE, 0, 0)
NPUOP(OP_REG_PC, 0, PC_REGISTER_AMOUNTS) # 驱动按 regcmd_count 回填
NPUOP(OP_40, 0, 0)
NPUOP(OP_ENABLE, 0x1D, PC_OPERATION_ENABLE) # 0x1D = bit0,2,3,4 → 触发 PC+CNA+DPU+DPU_RDMA
命令字的编码是 (op << 48) | (value << 16) | reg,其中 op 高位带上块标签
(如 0x0201 对应 CNA、0x0801 对应 CORE、0x1001 对应 DPU)[1][2]。一个
int8 matmul task 的完整 regcmd 是 126 个 NPUOP 字[1]。
每个 task 的 regcmd 以 DPU_S_POINTER = 0xE、DPU_RDMA_S_POINTER = 0xE 开头,
武装这两个块的乒乓寄存器组;kernel 在每个 task 前对 CNA 和 CORE 做同样的事,
并加上每核位[1][3]。
3. PC 与 block enable 位图
PC_OPERATION_ENABLE(0x0008)在手册与 Mesa 的寄存器表里都被描述为"只有
bit0 一个使能位,高位保留",但实测它是一个逐块的参与位图[1]。尾部那条
OP_ENABLE 选择哪些块运行:
| 程序 | 值 | 位置 |
|---|---|---|
| 卷积 | 0x1D |
bit 0,2,3,4 → PC / CNA / DPU / DPU-RDMA |
| 池化 | 0x60 |
bit 5,6 → PPU / PPU-RDMA |
两组互不相交,0x60 甚至不含 bit0。把卷积的 0x1D 塞进池化生成器、其余不变,
输出缓冲原封不动;反之亦然。所以 bit0 既不是充分条件也不是必要条件,只有"位图"
解释自洽——把尾部命令读成"本程序要触发的块清单"[1]。
4. ping-pong 与 delta task
寄存器状态在同一乒乓组内跨 task 保持[1]:
- 出厂形态下,task 落到的组是"再往前数第二个 task"写过的组;
- 一个 delta task 只写指针、三个缓冲地址和 4 字尾部;int8 matmul 的地址是
CNA_FEATURE_DATA_ADDR(0x1070)、CNA_DCOMP_ADDR0(0x1110)、DPU_DST_BASE_ADD(0x4020),只 9 字,而完整 task 是 126 字[1]; - delta 不能依赖上一个 job:寄存器文件跨 job、跨进程保持,调度器又决定 job 落在哪个核,所以上一个 task 是谁并不可知[1];
- 用户态 regcmd 绝不能写 CNA/CORE 的
S_POINTER:那需要 kernel 的每核位, 缺位或写别的核会在 24/24 次测试中让 task 永不完成、无中断、输出未写, 最终靠 kernel 的 500 ms 超时退场[1]。
连续链(batched submit)把 N 个 task 压成一次硬件 kick、一个完成中断[1]:
- 把各 task 的 regcmd 连续摆放(步长取偶数化后的字数);
- 改写每个 task 的尾部,把 PC 重定向到下一个 task(复用 count-4 处的惰性
OP_NONE位,嵌入PC_BASE_ADDRESS重定向 + 下一段的PC_REGISTER_AMOUNTS); - 设
PC_TASK_CON.TASK_NUMBER = N,PC 流式跑完 N 个 task 只报一次完成。
链内 task 是有序紧耦合的:前一个 task 的 WDMA 输出对后一个 task 的 ERDMA 读取 可见,因此链能表达跨 task 的数据依赖(例如 fp16 的 K 分块回加)[1]。
5. mainline rocket 的 uAPI
rocket 暴露四个 ioctl,作用于一个 /dev/accel/accelN[1][4]:
| ioctl | 作用 |
|---|---|
DRM_IOCTL_ROCKET_CREATE_BO |
分配 buffer object(有 DMA 地址 / IOVA) |
DRM_IOCTL_ROCKET_SUBMIT |
提交 job(含 tasks),异步返回 |
DRM_IOCTL_ROCKET_PREP_BO |
对 BO 做 cache 同步;在输出 BO 上作为栅栏等待 |
DRM_IOCTL_ROCKET_FINI_BO |
结束 BO 的使用 |
SUBMIT 是异步的,输出 BO 上的 PREP_BO 才是栅栏。一个易犯的 bring-up bug:
PREP_BO 的 timeout_ns 是绝对的 CLOCK_MONOTONIC 截止时刻,不是相对超时[1]。
6. matmul 作为 卷积
NPU 没有矩阵乘原语,librocketnpu 把
- K(收缩轴)→ 卷积的输入通道;
- N(输出轴)→ 输出通道,每个权重行 是一个 滤波器;
- M(行)→ 一张 的"图像"的空间位置。
单个输出 tile( 行 × 通道,收缩 )的数据流是[1]:
- CNA 特征加载:按行 / 面 stride 把 读进 CBUF(不重排);
- CNA 权重加载:把 读进 CBUF;
- CORE 跑 MAC 阵列,一次 passthrough 内跨 归约,累加进宽 CACC;
- DPU 把 写回 DDR(DPU-RDMA / 逐元素级在此参与)。
matmul 的激活与输出可以只改 6 个寄存器就保持行主序(GROUP_LINE_OFF、
行 / 面 stride、DST_SURF_STRIDE、DATA_CUBE_NOTCH、SURFACE_ADD)[1]。
几个实测约束:matmul 的 M 是卷积的空间高度,M<4 会算错,必须 (单行 matmul 要 pad 到 4 行)[1];一个卷积 task 至多 1022 输入行 × 2047 列[1]。
7. 提交路径上的性能要点
- 不绑 MAC。 在
rocket、600 MHz、权重常驻的工作点,matmul 只有约 460 GOP/s (约 fp16 峰值的 15%、int4 峰值的 4%),fp16 ≈ int8 ≈ int4,瓶颈在 DMA 与 每 job 派发,不在 MAC 阵列[1]。量化省的是内存,不是时间。 - 单行 decode 不要上 NPU。 M=1 的 GEMV 受 DDR 带宽限制,比 batched GEMM 慢约 82×,解码留在 CPU[1]。
- 时钟默认被钉在 200 MHz(1 GHz 规格的五分之一)。只能在驱动内、电源域起来 之后升频;安全操作点是 600 MHz(约 1.43× prefill),900 MHz 不再提速,冷启动 直接写 PLL 会把 SCMI 固件卡死[1]。
- 一核一 fd。 单 fd 的 job 串到一个核;用满三核要开 3 个及以上 fd[1]。
参考文献
[1] GREGORDINARY. Rockchip NPU reverse-engineering notes[EB/OL]. (2026)[2026-10-09]. https://github.com/gregordinary/rockchip-npu-notes/tree/81b0d17da0b8a472c1a1e6f7d2ddba6b34d10111
[2] GREGORDINARY. rocket-userspace: include/npu_hw.h[EB/OL]. (2026)[2026-10-09]. https://github.com/gregordinary/rocket-userspace/blob/f86cf52c666b4eddadc17d80d6c558b4067c0d6b/include/npu_hw.h#L25-L33
[3] Linux kernel. drivers/accel/rocket/rocket_job.c[EB/OL]. (2026)[2026-10-09]. https://github.com/torvalds/linux/blob/6c377d19d4a5116d9bec5203aa3c6c11523e7898/drivers/accel/rocket/rocket_job.c#L110-L140
[4] Linux kernel. include/uapi/drm/rocket_accel.h[EB/OL]. (2026)[2026-10-09]. https://github.com/torvalds/linux/blob/6c377d19d4a5116d9bec5203aa3c6c11523e7898/include/uapi/drm/rocket_accel.h#L14-L22
© 2026 Yang Huan · yanghuan9812@qq.com