Exp 2026 07 15 NPU Hard Wedge Circumstances - oRKLLM/ork-driver GitHub Wiki
Two distinct hard wedges observed in one session, both on the RK3588 single NPU hardware queue + IOMMU
under submit stress. Documented so a follow-up can find the exact resource that exhausts and add a guard.
Board 10.3.0.236, kernel 6.1.115-vendor-rk35xx-sram. Both were recoverable (no SPI corruption).
Trigger. tools/ork_xattn (fork feat/ork-async-stream) ran two ork backend contexts' FLASH_ATTN_EXT
graphs on two threads with no serialization — raw ggml_backend_graph_compute on each, deliberately
bypassing the coarse g_npu_queue_mu to stress-test the per-context attention-scratch refactor (Slice 2).
Symptom. dmesg: RKNPU: soft reset, num: 6 + FSC = 0x04: level 0 translation fault (IOMMU). The test
process stuck (D-state); SIGTERM did not drain it. A warm sudo reboot left the board pinging but sshd
never came up (~13 min; likely an fsck from the unclean NPU state) — needed the HA power-cycle in the end.
Root-cause hypothesis. The single NPU command queue cannot service concurrent submits from two IOMMU
contexts at once: the second context's submit references an IOVA the currently-programmed page table doesn't map
→ level-0 translation fault → wedge. (Consistent with the mainline accel/rocket model: the kernel programs
the PC/IOMMU from one job and re-arms per completion IRQ — it is not built for two fds racing the queue.)
Guard / avoidance. Serialize NPU submits across contexts. The async path's coarse g_npu_queue_mu does this;
the raw graph_compute path does not. Never run two ork graph_computes concurrently without the mutex. The
safe overlap model is ONE ork stream (NPU) ‖ another stream's NON-ork CPU work.
Follow-up questions. Is it purely IOVA-domain confusion (would a per-submit iommu_domain_id set + a
submit-level lock make CPU-parts-concurrent / submits-serialized safe)? Or deeper queue re-entrancy? Capture
dmesg at the exact fault with both contexts' domain ids.
Trigger. tools/int4_bench full run starting at M=512. The int4 path is 1-submit-per-ROW (the int4
multi-row M-scheduler reg 0x1040 is unfound) × per-group for grouped: M=512, K=2048, G=128 → ~512×16 ≈
8192 submits in a tight loop.
Symptom. 100% packet loss — no ping, no SSH (harder than Wedge 1's soft reset). Required HA power-cycle of the "Rock 5B Plug". Recovered cleanly in ~24 s, trees intact, no SPI corruption.
Isolation (guarded, single-path/shape via the new int4_bench <iters> <mode> [M K N G]):
| shape | path | result |
|---|---|---|
| M=1 K2048 N512 | int8 | 0.120 ms, safe |
| M=1 | int4 per-channel (1 submit) | 0.144 ms, RMS 10.1% (incoherent), safe |
| M=1 | int4 grouped (16 submits) | 0.638 ms, coherent, safe |
| M=16 | int4 grouped (256 submits) | 11.9 ms, safe |
| M=512 | int4 (~8192 submits) | HARD WEDGE |
So the wedge threshold is between 256 and ~8192 submits. M=1 decode (the intended MoE-expert use) is safe.
Root-cause hypothesis. The massive rapid int4 submit count exhausts a per-submit kernel/IOMMU resource (job slots / fence / IOVA mappings) faster than it is reclaimed → full wedge. Distinct from Wedge 1 (that was concurrency; this is count).
Guard / avoidance. Cap int4 to small M (M=1 is safe + intended); use the batch path (ORK_I4_MSCHED
stride-2) or int8 for large-M prefill; add errno=110 wedge-detect + timeout-drain. Never run raw int4 at
large M.
Follow-up questions. Find the exact submit-count threshold + which resource exhausts (dmesg at the
boundary; try M=64/128/256). Does the doorbell (ORK_I4G_DOORBELL, NONBLOCK — fewer blocking waits) shift the
threshold? The real fix is the unfound int4 M-scheduler (0x1040 multi-row) — finding it collapses the per-row
count entirely (see the ORK_I4_1040/ORK_I4_MREGS RE knobs).
RKNPU single-hardware-queue + IOMMU is fragile under submit stress on two axes — concurrent multi-context
(Wedge 1) and massive serial count (Wedge 2). Both are submit-management faults, both recoverable. Any
production path that touches either regime needs serialization (mutex) / a submit-count cap + an errno=110
wedge-guard, not raw dispatch. Board-ops: SIGTERM (never kill -9) + timeout on every NPU command; NPU wedge
(SSH up) → sudo reboot; hard wedge (no ping) → HA power-cycle the plug; if the plug cycle doesn't restore
ping → SPI reflash (physical).