PC chaining and the M tile schedule - oRKLLM/ork-driver GitHub Wiki
PC-chaining M-tiles & the rkllm weight-resident schedule (closed)
Reverse-engineering closure on how the RK3588 NPU chains multiple matmul programs into one submit, and whether rkllm's per-core "weight-resident fixed-M" schedule is a usable lever. Board-validated on RK3588 (10.3.0.236, governors=perf, core0). Preserved here from a WIP branch that is otherwise superseded by mainline chaining.
1. Many programs per submit is already unlocked — via PC-chaining, not subcore_task[]
The kernel accepts task_number = P > 1 when the programs are PC-chained — each regcmd links
to the next via words rc[216..219] (block 0x0101, regs 0x0010/0x0014 = "jump to next regcmd"),
with subcore_task[]={0,P}. Validated to task_number = 27, no wedge, no errno-110/timeout.
This is already shipping in the production prefill path (ORK_CHAIN_PREFILL, default on) and in
ork_mm_run_chain_i8. The earlier batch_probe "kernel rejects task_number>1" result was a
false conclusion — it submitted duplicate independent regcmds with no PC-link; the PC-link is
what the kernel requires. There is no missing subcore_task[] unlock.
2. mode0 (ork's own schedule) chains bit-exact; mode1 (rkllm weight-resident) is WRONG for M>1
Probe mtile_chain_probe 3584 128 2 — chain S weight-resident tiles (M=2), ork mode0 vs rkllm
mode1, K=3584:
| S | mode0 (ork) | mode1 (rkllm weight-resident) |
|---|---|---|
| 1 | OK bit-exact 98µs 19 GOPS (98.0µs/tile) | WRONG (256/256) |
| 2 | OK bit-exact 141µs (70.6µs/tile) | WRONG (510/512) |
| 4 | OK bit-exact 228µs (56.9µs/tile) | WRONG |
| 8 | OK bit-exact 401µs (50.1µs/tile) | WRONG |
| 16 | OK bit-exact 746µs 39 GOPS (46.6µs/tile) | WRONG |
| 27 | OK bit-exact 1222µs 41 GOPS (45.3µs/tile) | WRONG (6903/6912) |
The chain mechanism is sound (mode0 bit-exact at every S). mode1 is broken even single-task
(WRONG from M=2), so chaining is not the cause — the chain inherits mode1's brokenness.
3. Why rkllm's schedule is not a buildable lever
- The
mode1field overrides —0x1010 = 0x20(const, not16*rows),0x1044 = (K/64)*M,0x107c = 4*M, fixed M — do not compute M>1 correctly. Kept in-tree only as a documented-WRONG reference; the claim that a capture "validated mode1 as current/correct" is false. - rkllm's supposed "6 shared-weight tasks at distinct M-row offsets" does not exist: an exhaustive
108-reg diff across the 6 tasks + a two-prompt capture (39 vs 250 tokens, byte-identical
mc=[2,4,6,10,12,14]) proved0x102cis M-invariant and there is no per-task start/base offset register. The enabling offset the lever assumes doesn't exist. - rkllm prefills more tokens via more submits (39 tok → 1406 submits), same per-chunk floor as ork.
4. Where the real residual gap is (M=256 K=3584 N=3584, mc_prof)
- single-core: 25766 µs/matmul (18 submits, ~1316 µs/submit compute floor — not an ioctl floor)
- 3-core + CHAIN-PREFILL (production default): 9361 µs/matmul (3.05 ioctls/call, 18 chained programs/ioctl). The M-amortization + PC-chain lever is already captured.
The remaining gap to rkllm is per-submit compute efficiency — the per-row CBUF working set / the
R-cap R = pow2_floor(2*cbuf/K) = 16 at K=3584 — plus 3-core pipeline scheduling. Not
M-per-submit amortization. Raising R needs a smaller per-row working set (K-tiling CBUF residency),
a separate and harder lever.
Follow-up: the wide-K (K > kmax) K-slice optimum
The above is the full-K (K ≤ 4096, Bf) path. The wide-K K-slice accumulate path (e.g. 7B
ffn_down K=18944) has its own M-tile/schedule story, swept in
Exp-2026-07-27 K-Slice-Size Sweep:
the same sched=(Kp==1024||Kp==512) flag gates the big mg_max*64 M-tile there too, so KS=1024
is a provable optimum (all other slice sizes drop to sched=0 → a ~54-row M-tile → ~5× weight
re-stream). KS is not a lever; the live one is pipelining the host partial-accumulate (1.69× ceiling).
Related, now-superseded prototypes
Two sibling levers from the same 2026-06-29 exploration were integrated into mainline and their
branches dropped: (1) chain-ksplit for the K≤4096 K-split path — subsumed by main's general K-slice
chain-prefill; (2) single-core full-K M-tile chaining — subsumed by main's npu.c CHAIN-PREFILL
(PC-chain this core's M-tiles) plus the mg_max*64 M-tile cap raise (~2.1× single-core).