Exp 2026 07 15 NPU Hard Wedge Circumstances - oRKLLM/ork-driver GitHub Wiki

Exp 2026-07-15 — NPU Hard-Wedge Circumstances (for follow-up root-cause)

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).


Wedge 1 — Concurrent multi-context submits (attention) → IOMMU translation fault

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.


Wedge 2 — int4 large-M submit storm → hard wedge (no ping)

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).


Common thread

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).

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