Repository navigation
onnx: count the device weight-cache misses — the cache never hits, and it is one slot per (K, N) against 196 weights - #1247
Conversation
… packed weight per (K, N), 196 weights)
PR Reviewer Guide 🔍(Review updated until commit 4835ad3)Here are some key observations to aid the review process:
✅ Resolved findingsnpu/ort/onebit_npu_gemm.cpp:429-493Profiler Instrumentation The addition of |
|
Persistent review updated to latest commit 3127e92 |
|
Persistent review updated to latest commit 4835ad3 |
What
A counter, and the finding it produced. No behaviour change — the only executable difference is that the float
MatMulleg's profiler now reports how often it re-packs a weight, and the fused leg's line carries the same count.ONEBIT_EP_QDQ_PROF=1already priced the fused A8W8 leg (dev_split_*, #1114). The float leg had no instrumentation, and its per-call cost could not be read off the per-design probe: the probe timesgemm_f32with synthetic operands and a warm weight cache (3.63 ms/call for q at m=36), while the graph hands the same function real 32-row activations and measured 12.0 ms/call at model level. So:npu/ort/onebit_npu_gemm.{h,cpp}:gemm_f32takes an optionalF32Stats*(calls, weight-cache misses,packBms,go_rowsms) and fills it;Q8RawSplitgainswpacks. A plain call passes null and pays one branch, not a clock read.onnx/ep/onebit_ep_kernel.cpp: a second prof line under the same knob —f32_prof calls=… wpacks=… wpack_ms=… rows_ms=… call_ms=… other_ms=…— whereotheris printed rather than left to be subtracted; andqdq_profgainsdev_wpacks.docs/onnx.md: what the knob now prints for both legs, and what it found.What it found: the weight cache never hits, and that is structural
Three
npu-hold -l qdqtickets on a quiet box, each released cleanly — the float numbers below from the 04:07 ticket(
01793952433298170742.qdq, token after 12 s, 69 s of a 7-minute hold, load1 1.20), the A8W8 numbers from the 04:10 one(
01793952632360825046.qdq, token after 6 s, 57 s of a 6-minute hold, load1 1.17), and the 04:14 one(
01793952840214864204.qdq, 37 s) as the re-measurement that disproved the key hypothesis:1764 calls, 1764 re-packs, on both legs — two thirds of the float leg's run (14.1 of 21.1 s) inside
packB, 10 % of the fused leg's, because packing int8 bytes is cheap and quantising floats is not.The first hypothesis was wrong, and the fix it produced was measured and thrown away. I assumed ORT hands an EP node a different weight pointer per call, and wrote a stable weight identity for the cache (graph initializer captured at
CreateState, plan id for fused groups, fingerprint-guarded, plus aReleaseStatesplit the QDQ proof immediately caught as a double free against the fused path'sQdqPlan). Measured on the same graph, same designs, load1 1.52:calls=1764 wpacks=1764still, and the wall time moved only with the load (2461.11 ms/token against 2580.84). The reason is in this file already, as a comment on the kernel struct — the cache holds one packed weight per(K, N)kernel, and Qwen3-0.6B has 196 weights across five shapes (28 for q/o/down, 56 for k/v and gate/up), so successive calls of one shape evict each other. Keying the slot on identity cannot fix a one-slot cache. That revert is why this PR changes no behaviour: shipping it would have been a fix for a bug that does not exist.Making the cache hit needs a packed slot per weight — 440 MB of int8 projection weights resident (28·1024·2048 + 56·1024·1024 + 56·1024·3072 + 28·2048·1024 + 28·3072·1024), which the array does not have — or a cheaper
packB.wpack_msis the number that prices the second option, and it is now the largest single line the float leg has: ~8 ms per weight.Gates
CMAKE_BUILD_TYPE(the stricter one):ctest6/6 pass.ONEBIT_EP_NPU=ONRelease (~/wt/qdq-model-build.sh): builds,ctest5/5 QDQ pass,onebit_ep_prooffails and the two NPU probes skip — the recorded "NPU build with noONEBIT_EP_NPU_ELFSconfigured" state since 2026-10-31, not a regression.strings ep/libonebit_ep.socarries both new format strings (f32_prof …,dev_wpacks=%ld), i.e. the measurement above came from a binary that has them.python3 tools/copyright.py --check→ rc 0 (no new files).main;git log e0f0362d..origin/main -- onnx/ npu/ort/ npu/forward/is one commit (npu-pack: an imported container keeps the pack's fp32 scale/zero and stays servable, under layout q4nx-2 (closes #1185) #1242, npu-pack container scales — not the EP's float path), so nothing on this path moved underneath them.Head
3127e92e, baseorigin/main99c62594.