Skip to content

perf: ARM CPU conv kernels for DocTR detection#152

Open
olyasir wants to merge 4 commits into
temp-8828from
perf/doctr-detection-cpu-kernels
Open

perf: ARM CPU conv kernels for DocTR detection#152
olyasir wants to merge 4 commits into
temp-8828from
perf/doctr-detection-cpu-kernels

Conversation

@olyasir

@olyasir olyasir commented Jun 15, 2026

Copy link
Copy Markdown

What

ARM CPU conv-kernel optimizations for the DocTR DBNet detector when it runs on
the ggml CPU backend. This is the hybrid path used on Mali devices (Pixel 9
Pro / Immortalis), where full-Vulkan detection is pathologically slow, so the
addon routes detection to the CPU and keeps recognition on Vulkan.

All changes are ARM-CPU only and bit-identical in their numerical
formulas. GPU backends and non-ARM CPU paths are untouched.

Changes (3 files, ggml/src/ggml-cpu/)

ggml-cpu.c

  • Re-enable llamafile/tinyBLAS for the F16×F16 im2col GEMM on builds that
    compile in SVE / i8mm. Upstream #undefs GGML_USE_LLAMAFILE when those are
    present (the repacked quant paths are preferred for LLM workloads), which left
    the large conv GEMMs on the naive path. Runtime kill switch
    OCR_NO_LLAMAFILE=1 for A/B measurement.
  • CONV_2D→ADD→UNARY epilogue fusion and standalone ADD→UNARY pair fusion in
    the graph-compute loop (bias + activation applied in a single pass).

ops.cpp / ops.h

  • NEON-vectorised conv_2d_dw_whcn (was fully scalar).
  • Row-parallel im2col, plus a NEON-tiled, two-level-blocked 1×1 im2col transpose
    (vectorised f32→f16, paired-channel 32-bit stores, L1-resident tiles). Falls
    back to the channel-strided split when rows_total < nthreads, so 1-D /
    OH==1 convs stay multi-threaded.
  • Parallelised conv_transpose_2d and a contiguous-row UPSCALE fast path.

Measured

Pixel 9 Pro (Mali-G715), DocTR clinical_chemistry, warm, CPU backend:

detection
before 1.42 s
after ~0.83 s

Output unchanged: boxes=197, 12/12 expected clinical-chemistry keywords.

Base

Branched from and targeting temp-8828 (currently at the PR #148 Metal
CONV_2D_DW merge, 7bcd140f). The @qvac/ocr-ggml overlay port will pin this
branch to validate end-to-end on Device Farm before it's cut as a tag + a
registry port.

Speeds up the DocTR DBNet detector when it runs on the ggml CPU backend
(the hybrid path used on Mali, where full-Vulkan detection is pathological).
All changes are ARM-CPU only and bit-identical in their numerical formulas;
GPU backends and non-ARM CPU paths are untouched.

ggml-cpu.c:
- Re-enable llamafile/tinyBLAS for the F16xF16 im2col GEMM on builds that
  compile in SVE/i8mm (upstream #undef'd GGML_USE_LLAMAFILE there, which left
  conv GEMMs on the naive path). Runtime kill switch OCR_NO_LLAMAFILE=1 for
  A/B measurement.
- CONV_2D->ADD->UNARY epilogue fusion and standalone ADD->UNARY pair fusion in
  the graph-compute loop (bias + activation applied in one pass).

ops.cpp / ops.h:
- NEON-vectorised conv_2d_dw_whcn (was fully scalar).
- Row-parallel im2col plus a NEON-tiled, two-level-blocked 1x1 im2col
  transpose (vectorised f32->f16, paired-channel 32-bit stores, L1-resident
  tiles). Falls back to the channel-strided split when rows_total < nthreads
  so 1-D / OH==1 convs stay multi-threaded.
- Parallelised conv_transpose_2d and a contiguous-row UPSCALE fast path.

Measured on a Pixel 9 Pro (Mali-G715), DocTR clinical_chemistry, warm:
detection 1.42s -> ~0.83s on the CPU backend, output unchanged (boxes=197,
12/12 expected keywords).
@olyasir olyasir requested a review from a team as a code owner June 15, 2026 10:33
@github-actions github-actions Bot added the ggml label Jun 15, 2026
olyasir added 3 commits June 16, 2026 09:39
Remove the env-gated [CPUPROF] per-op wall-clock profiler from
ggml_graph_compute_thread (state decl, per-node timing, and the summary
log line). It was development instrumentation only (active under
OCR_CPU_PROF=1) and has no effect on the kernels. The CONV_2D->ADD->UNARY
and ADD->UNARY fusion paths this PR adds are unchanged.
Revert two pieces of experimentation residue to upstream, leaving only the
conv-kernel gains:

- llamafile/tinyBLAS re-enable (the SVE/i8mm #undef removal, the
  ggml_ocr_llamafile_enabled gate, OCR_NO_LLAMAFILE, the two call-site
  guards). The port builds GGML_LLAMAFILE=OFF so this is compiled out, and
  llamafile measured lf==nolf on ARM for this workload — no gain, and it
  would re-enable tinyBLAS for LLM on ARM if the flag were ever flipped.
- F16-src1 handling in mul_mat (assert widening + the f16->f32 chunked
  branch). It only enabled an f16 im2col against quantized (Q8_0) conv
  weights; the Q8 detector path was dropped, so it is unreachable for OCR
  (src1 matches vec_dot_type for f16/f16) and for LLM (f32 activations).

Kept: im2col row-parallel + 1x1 NEON fast path, depthwise NEON,
conv_transpose_2d parallelization, upscale fast path, and the
CONV_2D->ADD->UNARY / ADD->UNARY fusion.
Re-add the F16 src1 path in ggml_compute_forward_mul_mat (assert widening +
the f16->f32 chunked conversion) that an earlier cleanup removed. mtmd /
finetuning can feed F16 activations into a quantized mul_mat, so this path is
not safely dead. Restoring it makes the mul_mat code identical to the original
#152 for every consumer, eliminating any LLM behavioural difference. Only the
profiler removal + the compiled-out llamafile revert remain on top of #152.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant