Exp 2026 06 23 Chaining Resolution - oRKLLM/ork-driver GitHub Wiki

Multi-Matmul Chaining — Resolution (run_chain_i4 / run_chain_i8)

Date: 2026-06-23 Status: ✅ Resolved — both dtypes chain correctly; full make test green. Commit: ork-driver npu: fix multi-matmul chaining (run_chain_i4 / run_chain_i8)

Correction to prior 2026-06-23 entries

The earlier Hardware-Chaining-Zero-Overhead "breakthrough" (regcmd concatenation via CORE_CTRL=0x000f, single task, regcfg_amount=S*108) did not survive integration: ork_mm_run_chain_i8 built that way timed out in test_matmul (require mask 0x300, task counter 0x0 → NPU soft-reset). The task_number=1 PC-jump variant used by ork_mm_run_chain_i4 failed the same way. Treat the concatenation result as not reproducible in the integrated path.

What was actually wrong

Both run_chain_* diverged from the only validated multi-task path in the tree, i4_mcworker (the multicore M-chunk scheduler, which chains up to 16 tasks per submit and passes test_speed / the W4A4 suite).

The kernel mechanism (rockchip-linux/kernel develop-6.1, drivers/rknpu/rknpu_job.c)

rknpu_job_subcore_commit_pc for a submit batch:

task_end = task_start + task_number - 1;
first_task = &task_base[task_start];  last_task = &task_base[task_end];
REG_WRITE(first_task->regcmd_addr, RKNPU_OFFSET_PC_DATA_ADDR);
REG_WRITE((first_task->regcfg_amount + RKNPU_PC_DATA_EXTRA_AMOUNT + pc_data_amount_scale-1)
          / pc_data_amount_scale - 1, RKNPU_OFFSET_PC_DATA_AMOUNT);
REG_WRITE(last_task->int_mask,  RKNPU_OFFSET_INT_MASK);
REG_WRITE(((0x6|task_pp_en) << pc_task_number_bits) | task_number, RKNPU_OFFSET_PC_TASK_CONTROL);
REG_WRITE(0x1, PC_OP_EN); REG_WRITE(0x0, PC_OP_EN);
  • The kernel programs only first_task and last_task; the HW PC-chains through the middle.
  • PC_DATA_AMOUNT (how many words the HW fetches for the FIRST task) is derived solely from first_task->regcfg_amount — get it wrong and the HW mis-fetches task 0, never reaches its boundary, the task counter never increments → the exact task counter: 0x0 timeout.
  • Completion: the kernel polls a HW task counter (pc_task_status & pc_task_number_mask) until it reaches task_number. So task_number MUST equal the number of chained regcmds.
  • RK3588 config: pc_data_amount_scale=2, pc_task_number_bits=12, pc_task_number_mask=0xfff, pc_task_status_offset=0x3c, max_submit_number=4095.

The working recipe (mirror i4_mcworker)

  1. Synthesize S complete regcmds (one per matmul, synth_i4/synth_i8) into one persistent buffer at stride i*REGCMD_*_N*4.
  2. Patch each non-final regcmd's chain-control tail (words rc[216..219], both dtypes) to jump to the next regcmd: rc[216]=0x0010|(next&0xffff)<<16; rc[217]=0x0101<<16|(next>>16)&0xffff; rc[218]=0x0014|0x0037<<16; rc[219]=0x01010000;. The final regcmd keeps the template default (0,0,0x14,0x01010000 = raise completion interrupt).
  3. One rknpu_task per regcmd: regcfg_amount = 116 (i4, 232-word template) / 108 (i8), regcmd_addr = base + i*REGCMD_*_N*4, enable_mask=0xd, int_mask=0x300, int_clear=0x1ffff.
  4. Submit: task_number = S, subcore_task[0..2] = {0,S}, flags=0x5, fence_fd=-1, core_mask = 1<<core, timeout=60000 (leaving it 0 was a direct cause of the instant timeout), a cold-buffer warmup rep, and rknpu_submit_ioctl (self-healing reset on failure).

Results (RK3588, board khazad-dum)

  • test_chain_i4 (same weight, S=2) → OK: chain_i4 output verified.
  • test_matmul ork_mm_run_chain_i8 (four different weights, varying M, S=4) → ok chained S=4 (varying M, independent matmuls) mism=0. Confirms different-weight chaining.
  • Full make test: ALL TESTS PASSED (W4A4 API, layer, decode, model 1/12, test_speed 3-core 1.88x, test_chain_i4). No normal-path regressions.

Board-recovery note

The chaining experiments can soft-lock the NPU. A wedged NPU is not cleared by a warm reboot (reloads the driver but not the NPU IP power/clock domain) — it needs a full power-cycle. rknpu is built into the vendor kernel (no module reload).

Next steps

Wire run_chain_i8/run_chain_i4 into the ggml-ork decode path (MoE / multiple small M=1 matmuls per layer) and measure end-to-end vs the per-matmul submit floor.

⚠️ **GitHub.com Fallback** ⚠️