[PATCH v7 08/10] accel/rocket: add RK3576 NPU (RKNN) support
Igor Paunovic
royalnet026 at gmail.com
Sun Aug 16 11:53:59 PDT 2026
Hi Jiaxing,
I ran the discriminating counts on RK3588. Details below, but first a
source-level observation that changes what my result can tell you.
Neither mesa-25.3.0 nor current main contains the modulo form in
rkt_regcmd.c. In both, BS_OW_CFG (0x4050) is emitted conditionally on
operation->depthwise only, with no dependence on output channel count:
if (operation->depthwise) {
EMIT(REG_DPU_BS_OW_CFG, DPU_BS_OW_CFG_SIZE_E_2(3) |
DPU_BS_OW_CFG_SIZE_E_1(3) |
DPU_BS_OW_CFG_SIZE_E_0(3)); /* 0x36c */
} else {
EMIT(REG_DPU_BS_OW_CFG, DPU_BS_OW_CFG_SIZE_E_2(1) |
DPU_BS_OW_CFG_SIZE_E_1(1) |
DPU_BS_OW_CFG_SIZE_E_0(1)); /* 0x124 */
}
(mesa-25.3.0, src/gallium/drivers/rocket/rkt_regcmd.c around line 234;
unchanged in main as of 26.1-branchpoint.)
So I take it the (oc % 32) expression is from one of your 102 commits,
fitted from vendor captures? That would also explain the extra bits in
your two constants: decoded against registers.xml, 0x80011111 vs
0x80011011 differ only in bit 8, which is the LSB of SIZE_E_2 (bits
8-10). Your working-at-56 form has SIZE_E_2=1 and the failing form has
SIZE_E_2=0 - and upstream's non-depthwise value always has SIZE_E_2=1.
The three data points are consistent with SIZE_E_2 being what matters,
rather than the modulo-vs-parity predicate as such. Your values also
carry RGP_CNTER=8 (bit 31), OW_SRC=1 (bit 0) and bits 12/16 in the
reserved range, which upstream never sets; if those came from a vendor
capture it may be worth checking which of them RK3576 actually needs.
Now the measurement. My runs are with upstream Mesa (main,
26.1-branchpoint-5505, clean checkout), i.e. the constant 0x124 form,
on RK3588 with the in-tree rocket driver (7.2-rc7, rockchip-devel
base). Standalone minimal models: CONV_2D 1x1, 64 input channels, 8x8
surface, uint8 asymmetric quantisation in the mobilenet style, output
zero point 0 so the hardware's clamp-at-zp cannot mask anything:
1x1 conv, 64 in, 56 out: 56/56 channels within +/-1 (47
bit-exact), maxdiff 1
1x1 conv, 64 in, 88 out: 88/88 channels within +/-1 (81
bit-exact), maxdiff 1
1x1 conv, 64 in, 120 out: 120/120 channels within +/-1 (106
bit-exact), maxdiff 1
Reference is the raw CPU implementation per channel (not clamped to the
zero point - same lesson as operator 28). No channel had a constant
reference, so the matches are computed rather than trivial, and no
channel sat pinned at the output zero point. The three jobs also
happened to land one per NPU core (per-core IRQ counters went 0->1),
so the result is not specific to a single core.
Since upstream emits the same word for every oc, a
clean result at 56/88/120 says RK3588 does not need an oc-dependent
toggle at 0x4050 at all, at least for these shapes - which is the third
possibility your two forms could not distinguish. It does not tell us
which of your two fitted forms is closer to what RK3576 wants, but it
does suggest comparing your fitted values against the field composition
above rather than as opaque constants.
Happy to run other shapes if that helps narrowing it down.
Best regards,
Igor
On Sun, Aug 16, 2026 at 6:12 AM Jiaxing Hu <gahing at gahingwoo.com> wrote:
>
> Hi Igor,
>
> Thank you for running it, and for the unclamped reference.
>
> Your 1001 does not discriminate between the two forms, which I should have said
> when I described them. DIV_ROUND_UP(1001,16) is 63, odd, so the parity form
> gives 0x80011011 there and so does oc % 32. They differ only where a count is a
> multiple of neither 32 nor 16. On RK3576 the failing one was 56, and 88 or 120
> would do as well. So your clean result rules nothing out, and I am not reading
> it as support either way.
>
> On my side that register turned out to be the whole of it. A 1x1 layer with 56
> output channels had timed out in every round it ever ran, 32 of 56, and with
> the parity form it is 56 of 56 every channel correct, with the old form
> reproducing the timeout in the same log.
>
> You are right about perch.py and there were three of them rather than one. The
> inner maxdiff, the interior statistics and the raw row dump all assume a
> surface with an interior, and all three raise on a 1x1x1001 classifier after
> the useful lines have printed. Fixed here, and checked on the host at 1x1x1001,
> 5x5x8 and 56x56x64 before flashing, which is a check I should have been running
> all along.
>
> And I will re-score operator 28 against the unclamped output. The max(cpu, zp)
> reference does move channels that are not wrong, and reading an empty
> convolution off it is the kind of mistake I would rather not make twice.
>
> Jiaxing
More information about the Linux-rockchip
mailing list