Exp 2026 07 14 Mode Transition Layer - oRKLLM/ork-driver GitHub Wiki
Exp 2026-07-14 β Mode-transition layer (ork_npu_enter)
Status: Phase 1 landed (behavior-preserving) Β· Branch: feat/mode-transition-layer (off
main @ 93a8595) Β· Board: RK3588 10.3.0.236.
Related: Optimization Roadmap Β§Mode-transition layer Β·
Exp-2026-07-12-NPU-Mode-Transition-Task-Descriptor Β· Exp-2026-07-13-Linear-Attention-Scans-on-NPU.
Motivation
The RK3588 NPU's regcmd datapath is stateful: a "mode" (precision + schedule) is tracked in
software by c->last_dt, plus warm flags (warmed, per-core mwarm[]) and buffer-size caches
(ccsz, per-core mccsz[]). Moving between modes can require an RKNPU_ACT_RESET ioctl (the
first-int8-submit wedge guard), a cold 2-pass re-warm (fresh-output-buffer priming), and/or a buffer
realloc β but not always, and the keep-warm knobs (ORK_SSM_KEEPWARM int8βfp16, default on;
ORK_MIXED_NOTHRASH intβint, default off) suppress the churn where it's safe.
That policy had been copy-pasted inline into ~16 run/stream/chain/int4 entrypoints, each written slightly differently and quietly drifted apart. There was no single owner of "what does moving from mode X to mode Y require" β which is exactly why the int8βfp16 keep-warm fix had to be hand-patched into 5 separate sites, and why a new interleave consumer (on-NPU softmax/attention, the chain assembler) keeps re-paying that scatter cost.
Design
One function, ork_npu_enter(c, to_marker, profile), driven by an exhaustive XSPEC[] policy
table (src/npu.c). Every run path calls it first; its inline reset collapses to a single call.
Key realization that makes the NxN fromβto matrix tractable: from (= c->last_dt) enters the
policy ONLY through the ORK_I8_LIVE / ORK_INT_DT / ORK_KW_DT predicates. So a row is keyed by
(target, caller-scope), not by an enumerated from β collapsing the matrix to one row per
historical site. Modes: { COLD(-1), F16(0), I8(1), I4(2), I8_CHAIN(3), I4_CHAIN(4), I4_STREAM(5) },
plus a transient SDP (activation/ewmul, no stored marker).
A profile row is { kwp, rst, wtg, wc, stg, sc, setdt }:
kwpβ keep-warm predicate selector (KWP_MC,KWP_SC,KWP_NTI,KWP_NTL,KWP_F16,KWP_NONE).rstβ reset condition (RC_NEVER,RC_NOTKW,RC_I8ENTRY,RC_NOTLIVE,RC_NOTLIVE_NOTKW,RC_ALWAYS).wtg/wcβ warm-clear target (TG_SCALARwarmed/TG_PERCOREmwarm[]/TG_BOTH) and condition (WC_*).stg/scβ size-clear target (ccsz/mccsz[]) and condition.setdtβ whether it writesc->last_dt.
ork_npu_enter returns 1 iff a real transition fired (0 on the from==to no-op), so the two sites
that gate a caller-local warmup flag off the transition (XP_I4_INCR's warm, XP_I4_STREAM's
cold) stay byte-identical. Each case in the function is a literal transcription of the predicate
the original site used β so behavior is byte-identical by construction, across all knob settings,
not merely at default.
The 12 profiles (16 sites consolidated)
| profile | sites replaced | target | notable policy |
|---|---|---|---|
XP_MC_MM |
run_multicore |
F16/I8 | reset only entering I8 from non-int8-live; keep-warm MC (nothrashβ¨f16) |
XP_SC_MM |
run single-core |
F16/I8 | as above, f16-keep-warm only, scalar scope |
XP_CHAIN_NT |
run_chain_i8_impl, ork_npu_chain_progs |
I8_CHAIN(3) | nothrash-only keep-warm (see drift #2) |
XP_STREAM_I8 |
run_stream_i8 |
I8_CHAIN(3) | reset !I8_LIVE && !kw, clears both warm scopes |
XP_STREAM_I8B |
stream int8 variant | I8_CHAIN(3) | reset !kw (see drift #3) |
XP_STREAM_F16 |
stream f16 (Γ2) | F16 | reset !kw (stream fp16 DOES reset on entry) |
XP_I4_MC |
run_i4_mc, run_i4_grouped |
I4 | mccsz cleared on !nothrash |
XP_I4_MWARM |
run_i4_incr_mc, run_i4_bchain, run_i4_cbatch |
I4 | mwarm-only, no size clear |
XP_I4_INCR |
ork_mm_run_i4_incr |
I4 | no c-> clears (caller-local warm) |
XP_I4CHAIN |
ork_mm_run_chain_i4 |
I4_CHAIN(4) | unconditional reset + warmed + ccsz |
XP_I4_STREAM |
stream i4 | I4_STREAM(5) | unconditional reset + both warm scopes |
XP_SDP |
(defined, not wired β Phase 2) | transient | unconditional reset, setdt=0 |
Validation (byte-identical gate)
A refactor that touches every matmul/activation path must not perturb a single output. Gate:
make test on the board β exercises fp16 (test_matmul/layer/decode/model), int8, int4
(i4/test_chain_i4/perplexity_i4), stream (test_stream_interleave), int8βfp16 SSM
(test_ssd_chunk_npu), the SDP ops (test_ewmul_{i8,f16,i16}/test_silu/test_gelu/test_add), and
test_mode_transition. Run once per keep-warm knob (default, ORK_SSM_KEEPWARM=0,
ORK_MIXED_NOTHRASH=1). Cross-check with make mode_probe (transition matrix unchanged) and
ORK_DEBUG_RESET=1 (ACT_RESET count/sites unchanged β the "28β1" keep-warm behavior intact).
Result: (filled in on landing β see the commit message / roadmap progress log.)
Phase 2 β chaining mechanism as transition state + real-pipeline findings (2026-07-14)
ork_npu_enter now takes a 4th arg chain (enum ork_chain_kind: OCK_NONE per-matmul/batch,
OCK_SW run_stream_*, OCK_HW run_chain_i8, OCK_FUSED run_chain_i8_ffn static graph), recorded
as c->last_chain. This makes the chaining mechanism explicit transition state so the policy can branch
on it for the few handoffs where the mechanism genuinely matters β and ignore it (the common case)
otherwise. Behavior-preserving (records state; no branch yet), make test byte-identical.
Real-pipeline bench via ork_bench (RK3588, governors perf, refactored ork-driver):
| run | prefill tok/s | decode tok/s | XPROF | coherence |
|---|---|---|---|---|
| qwen3-1.7b-q8_0 default | 59.99 | 5.89 | MC_MM only |
β coherent |
qwen3-1.7b ORK_FUSE=1 |
63.01 | 5.87 | MC_MM only |
β (fusion neutral) |
qwen3-1.7b ORK_FFN_CHAIN=1 |
68.64 | 0 (broke) | died before CHAIN_NT |
β PRIME_FD domain-alloc |
The sw/hw/fused chain profiles are dormant in a real dense pipeline (ork_dispatch_i8 never fires
β the ggml graph interleaves rope/norm so the chain-walk never reaches β₯2 consecutive independent int8
nodes; MoE experts are CPU by default; ORK_FFN_CHAIN fails on footprint). They're exercised only by
mode_probe/test_ssd_chunk_npu and narrow configs. The hunch is confirmed for the fused static
graph: it chains differently (in-chain SDP/LUT, heavy footprint, fragile) and is distinguished as
OCK_FUSED β not to be blindly converged with the pure-matmul hw chain. Bonus: the refactored driver
ran the real model coherently end-to-end.
Phase-2 catalogue (drifted / specially-motivated sites, left for 1-by-1 evaluation)
The premise: a transition that doesn't map cleanly is probably a bug β the whole motivation for the
refactor β but some drift is intentional, so each is decided individually (a one-row XSPEC edit,
re-validated). Do not flip blind.
Guiding principle β which version is correct? The tests passed even with the drifted glue, so correctness cannot arbitrate. The drift itself is the bug: when two paths do the same logical transition differently, converge them to one best-performing version even if all are correct (perf + single-source-of-truth decide the survivor), whether or not the diverging path is currently exercised β after confirming they really are the same transition. Tiebreakers for the survivor, in order: (1) if candidates are otherwise byte-identical, prefer the best-performing version (bench it); (2) use the experiment logs + board-quirks data to discern/hypothesize; (3) when logs contradict, favor the most recent entry (later findings are more likely accurate); (4) when a contradiction reveals a wiki entry is wrong, fix or flag it β edit the entry to be correct (or add a dated note), keeping the wiki self-correcting.
-
SDP / activation ops β RESOLVED 2026-07-14: NOT a bug; do NOT wire
XP_SDP. (Correcting this page's own earlier framing, per the self-correcting-wiki principle.) The claim that SDP leavinglast_dtuntouched causes the fp16-mmβSDPβfp16-mm wedge was wrong. The wedge was thec->taskLUT-descriptor poisoning β a separate axis (nuance #1) β already fixed in 98c00b1; Exp-2026-07-12-NPU-Mode-Transition-Task-Descriptor states plainly "the wedge has nothing to do withlast_dt," andtest_mode_transition(its regression) passes. Boardmode_probe(2026-07-14) confirms every SDPβmatmul pair is SAFE with no reset, and that forcing one costs ~105Β΅s/transition for zero correctness gain (SDPβint8-matmul:noneβ0.3ms vsinvalidate/resetβ105β107ms). FlippingXP_SDP.setdt=1would re-introduce the exact churnORK_SSM_KEEPWARMeliminates β so don't (guiding principle #1). SDP ops correctly leavelast_dtuntouched;XP_SDPstays reserved/unused. Follow-up win (2026-07-14): removed the element-wise SDP entry reset. Theewmul_i8(no reset)/ewmul_f16(reset) asymmetry was investigated (ORK_SDP_NORESETgate): the fp16/int16 element-wise ops (ewmul_f16/i16,add_f16/i16) each did an unconditional ~107 msACT_RESETtheir int8 twins already skipped. Removed it β coherent (test_ewmul_f16/i16,test_addpass), wedge-free after a matmul (mode_probeSAFE), fullmake testgreen incl. in-graph SSM. Measured:test_ewmul_f163436β53 ms,ewmul_i164292β44 ms,add1419β50 ms β a real win on the SwiGLU-ewmul / residual-add / SSM-elementwise critical path. Default on;ORK_SDP_NORESET=0restores. (act_lutsilu/gelu/exp/rsqrt already reset only conditionally.)SDP op β MM_F16 (none/inval/reset ms) MM_I8 (none/inval/reset ms) EXP_I16 0.2 / 0.3 / 0.3 β all ok 0.3 / 105 / 107 β all ok SILU_I16 0.2 / 0.3 / 0.3 0.4 / 105 / 107 EWMUL_I16 0.2 / 0.3 / 0.3 0.5 / 107 / 107 EWMUL_F16 0.2 / 0.3 / 0.3 0.4 / 107 / 107 ADD_F16 0.2 / 0.3 / 0.3 0.5 / 107 / 107 All SAFE;
none(keep-warm) is fastest everywhere β bold = the chosen (current) behavior. -
Chain profiles honor
ORK_SSM_KEEPWARMβ FIXED 2026-07-14 (β ~105 ms/transition win).XP_CHAIN_NT(KWP_NTL+RC_NOTLIVE) ate a full ACT_RESET when a chain followed an fp16 op, where the stream profiles kept warm. Since the path is dormant in a dense pipeline, a manufactured fp16βint8-chain sweep (chain_xition_probe, wide K/N/M) measured it: chain(hw) reset-cost ~53,538 Β΅s avg (individual 104β107 ms) vs stream(sw) ~0, both coherent β the reset was pure drift. Converged toKWP_MC+RC_NOTLIVE_NOTKW; re-run: chain(hw) reset-cost β β1.1 Β΅s, all coherent,make testgreen. -
XP_STREAM_I8/I8Bunified β FIXED 2026-07-14. Gratuitous (non-default-knob) drift; converged to the same policy, collapsed to one profile (XP_STREAM_I8),XP_STREAM_I8Bremoved. -
Diagnostic forced-fp16 resets (
ork_ssd_fused,ork_ssd_probe_rawmm/fusedmm_f16) β ASSESSED: intentional unconditional reinit in board-only diagnostics, not a precision transition; left as-is. -
Conditional prime resets (
ORK_I16_RESET; fused-SiLU LUT!ORK_I8_LIVE || ORK_PROBE_RESET) β ASSESSED: correct as-is; env/warm-conditional and correctly leavelast_dt(consistent with the SDP finding above). Not drift. -
int4 clear-gating β RESOLVED: mechanism-legit, not a bug. Built
tools/i4_xition_probe.c(manufactures{fp16,int8,int4-live}βint4across a wide K/N/M sweep). All four int4 mechanisms βrun_i4(XP_I4_MC),run_i4_incr(XP_I4_INCR),run_chain_i4(XP_I4CHAIN),run_stream_i4(XP_I4_STREAM) β are 36/36 coherent across every predecessor/shape.XP_I4_MCclearsmccszto resize its persistent per-core output on an int4 entry; the batch/incr paths size buffers per call β a legitimate buffer-lifecycle difference, not gratuitous drift β no convergence. (Aside: an earlier probe variant showed a repeated back-to-backork_mm_run_chain_i4miscompute β a possible re-warm gap on consecutive chain_i4 calls, distinct from the transition question; flagged for the experimental int4-chain.)
Test-suite + build + CI (2026-07-14)
Infrastructure that landed alongside the transition work:
- Static golden-checksum tests. Inputs are fixed-seed deterministic β the NPU output is a constant, so
the reference-bound tests assert an fnv64 checksum of the NPU output vs an embedded golden instead of
recomputing the O(MΒ·NΒ·K) CPU reference each run; the reference is kept for regen/diagnosis
(
ORK_REGEN=1/ORK_FULL_REF=1).test_matmul195β5 s,quant39.8β1 s,test_sn37β1 s,model 123.1β0.4 s; fullmake test~10-11 min β ~33 s. - Manufactured-transition probes.
chain_xition_probe(sw/hw chain) andi4_xition_probe(int4) β exercise the transition paths that are dormant in a real pipeline;chain_xition_probeis inmake test. - Build. CORE (
npu.c~9.7k lines) is compiled once into shared-fPICobjects (was recompiled ~30Γ inline), andCCis wrapped with ccache β full build ~5 min β ~20 s, incremental npu.c-edit rebuild ~9 s. - CI board-validation gate. ork-driver's tests need the NPU (CI runners lack it).
make teston the SBC writestests/sbc_attest.txt= sha256 of the NPU-output-determining.csources + golden-bearing tests; CImake check-attest(no NPU β sha256 + grep) fails if the tree hash differs, catching a commit not board-validated before push.version-bump.ymlruns it before the bump (attestation precedes the version bump);.github/workflows/sbc-attest.ymlgates pushes/PRs. Details: AGENTS.md Β§"Mode-transition layer" + Β§3.
How to modify / add a transition
See AGENTS.md Β§"Mode-transition layer". Changing a policy = editing one XSPEC[] row; adding an
op/dtype = a new XP_* row + one ork_npu_enter(c, marker, XP_*, chain) call. Validate on the board
(make test), then commit the refreshed tests/sbc_attest.txt so CI passes.