Exp 2026 10 11 Weight Ownership Under Mmap - oRKLLM/ork-driver GitHub Wiki
Exp-2026-10-11 β ggml-ork disowned every weight whenever mmap was on
Board: RK3588 10.3.0.236, kernel 6.1.115-vendor-rk35xx-fence, governors verified
(DDR 2112 MHz / A76 2400 MHz, both performance), NPU guard held, 0 faults.
Stack: llama.cpp-rockchip diverged-history @ 35e76dbd0, ork-driver 1.0.168, built in
~/llama-pinned with -DGGML_ORK=ON -DGGML_OPENMP=ON, Release.
Model: Qwen3-1.7B-BF16.gguf (arch qwen3), ork_bench β¦ 128 64 128, 2 reps, warm.
Result
| before | after | |
|---|---|---|
| tensors moved to the CPU backend | 311 | 115 |
ORK_Weights model buffer size |
absent | 2688.46 MiB |
| bytes copied into anonymous RAM | β | 0 (all mmap) |
| prefill tok/s | 165.85 / 164.57 | 166.21 / 165.51 |
| decode tok/s | 1.10 / 1.10 | 1.10 / 1.10 |
ork_ppl 4Γ512, ubatch 512 |
1.6587 (NLL 0.506032) | 1.6587 (NLL 0.506032) |
| ork residence | 196/196 weights, 2.30 GiB, 3 domains | identical |
Ownership moved; nothing else did. PPL is identical to six decimals and prefill is flat inside the run-to-run spread (the two before reps differ by more than the beforeβafter delta).
Mechanism
The device iface set .get_host_buffer_type to the same function as .get_buffer_type.
llama_model_loader::create_tensor rewrites any buft equal to the device's host buffer type to CPU
when mmap is on β a rule that exists so a pinned-host staging buffer is never used as model storage.
Because ork's real buft compared equal to its own host buft, that rule fired on ORK_Weights itself
and handed every weight to the CPU backend.
The iface also contradicted the device's own get_props, which already reports host_buffer = false.
The loader trusts the iface, not the props, so the props being right changed nothing. NULL is the
honest answer and ggml_backend_dev_host_buffer_type() already handles it.
Compute was never stranded β ownership was. ork runs buffer-less via offload_op, so matmuls still
reached the NPU (196/196 weights resident before the fix). What was lost is the scheduler's
weight-locality signal.
The two halves have to land together
Dropping the alias alone makes things worse. Weights then arrive through
ggml_backend_ork_buffer_type_alloc_buffer, which is posix_memalign plus a set_tensor memcpy of
the whole model β and over a stub source (whose packed tensors are holes) that copies gigabytes of
zeros into anonymous RAM.
So buffer_from_host_ptr had to change at the same time. It returned
ggml_backend_cpu_buffer_from_ptr, whose buft reports CPU β so the zero-copy mmap path would have
handed the weights straight back to the CPU backend anyway. It now returns a buffer carrying
ORK_Weights over llama.cpp's mmap window, with a free_buffer of NULL because the mapping is
borrowed and freeing it would unmap the model out from under every other view of it.
Make ownership observable, and name the mechanism
Ownership used to be inferable only from an ORK_Weights model buffer size line not being printed
β a signal that is a thing not happening, which is the kind nobody notices. There is now a positive
line, and it names how the bytes arrived, because "owned via a multi-GiB memcpy" and "owned via an
mmap window" are very different outcomes that would otherwise print the same word:
[ORK BUFTYPE] weights on ORK_Weights via mmap (zero-copy, borrowed): 2688.46 MiB
(cumulative: mapped 1/2688.46 MiB, copied 0/0.00 MiB)
copied 0/0.00 MiB is what makes "the memcpy trap was avoided" a measurement rather than an
argument.
Caveats
CPU_Mappedstays at 3281.97 MiB, so the two buffers sum to more than the 3.21 GiB model. Both are mmap windows over the same file (llama.cpp maps a per-contextfirst..lastrange and they overlap), so this is not double RAM βfreeshowed no anonymous growth. Inferred, not isolated.- Validated on one model (1.7B BF16, arch
qwen3). int4/W4A4 and MoE are untested under this change, and it alters routing for every model. - Decode at 1.10 t/s is BF16-on-CPU and is a weak signal; prefill is the routing-sensitive axis.
ork_ppl1.6587 is not a quality number β the fixture is a repeated sentence, so it is predictable by construction. It is used here only as an equality check between two builds.
Side finding β the backend's only automatic correctness gate is dead
tests/test-ork-cache.cpp fails on pristine origin/diverged-history, verified by checking out
the unmodified file, rebuilding and re-running. It is not a regression from this change.
Run 1 (X = 1.0): dst[0] = 0.000000 Expected Run 1: 64.000000
Run 2 (X = 2.0): dst[0] = 0.000000 Expected Run 2: 128.000000
What a 20-minute time-boxed look established:
0.0twice is not the stale-activation-cache bug the test was written to catch β that would be64.0twice.- The op is not being declined and skipped:
ORK_VERBOSEshowsSTART mul_mat_i8on both runs and onepacked weight β¦ K=128, N=128. The backend reaches and executes the matmul path. ORK_MINM=1does not change the result, so the M=1 decode gate is not the cause.ggml_backend_ork_graph_computedoes returnGGML_STATUS_FAILEDwhenmul_mat_i8fails β but the test never checks the return value, so a backend failure and a wrong answer are indistinguishable from its output. That is a defect in the test whatever else is true.- The backend is not miscomputing matmuls generally on this binary:
ork_pplis bit-identical across the ownership change, 196/196 weights resolve, generation is coherent.
Not established: why dst stays zero. Leading hypothesis, unproven: the M=1 / F32-source /
no-pack path inside mul_mat_i8 returns false or delegates to a route that needs a format this
synthetic tensor does not have, leaving dst untouched. Distinguishing that needs an M>1 variant of
the test or instrumenting mul_mat_i8's exit.
Read this as "the gate is dead", not "the backend miscomputes a 128Γ128 matmul". CI never runs it
(LLAMA_BUILD_TESTS=OFF), which is why it has been red unnoticed. It also provokes
RKNPU: soft reset, num: 6 β on the pristine build too; the driver self-heals.
What to use as a gate instead
test-backend-ops support -b ORK enumerates ORK (Backend ORK: OK, 2/2 backends passed) and is
safe: support mode only calls supports_op, issues no submits, so there is no wedge risk. It
gives zero MUL_MAT coverage β 0 of 1638 cases claimed, since ork's K%32 / N%16 / Mβ₯8 and
source-type gates decline them all β so it cannot gate matmul correctness. It is, however, a cheap
and safe detector of supports_op changes, which is what any future N-cap or pack-policy work
needs. Baseline recorded: 0/1638.
test-backend-ops in test mode (which executes ops) was deliberately not run: firing thousands
of generated shapes at the NPU unsupervised, with a test already provoking a soft reset, risks a wedge
whose recovery needs a physically-present operator.