ggml-rocket: min_batch floor — keep M<32 (decode) on CPU

llama-bench A/B on boltzmann (gemma-4-E2B-Q8_0, A76-pinned, -t 4) showed the
NPU path HALVED decode: tg128 CPU 7.97 vs Rocket 3.88 t/s, because every M=1
matmul paid the full per-tile BO-alloc/mmap/submit/wait/close round-trip while
decode is bandwidth-bound (the vendor rknpu2 campaign found the NPU only +7%
there even at full offload). Gate supports_op on M>=ROCKET_MIN_BATCH(32),
mirroring ggml-blas, so small batches stay on CPU.

After: pp512 40.32 (was 40.52, +12% vs CPU 35.95), tg128 8.34 (was 3.88, now
>= CPU). Strictly >= CPU on both phases instead of a 2x decode regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
This commit is contained in:
mfritsche
2026-07-14 11:45:05 +02:00
parent 5550257f4c
commit 698c6b731c
+6
View File
@@ -40,6 +40,7 @@ extern "C" {
#define ROCKET_TILE_M 16
#define ROCKET_TILE_N 128
#define ROCKET_K_MAX 8192
#define ROCKET_MIN_BATCH 32 // below this, CPU wins (decode is bandwidth-bound)
struct ggml_backend_rocket_context {
int fd = -1;
@@ -350,7 +351,12 @@ static bool ggml_backend_rocket_device_supports_op(ggml_backend_dev_t dev, const
case GGML_OP_MUL_MAT:
{
const int64_t K = src1->ne[0]; // == src0->ne[0]
const int64_t M = op->ne[1]; // batch rows (tokens)
// Offload only when the NPU can amortise its per-tile submit
// overhead: decode (M=1) is bandwidth-bound and strictly faster on
// CPU, so keep small batches there (mirrors ggml-blas min_batch).
if (M < ROCKET_MIN_BATCH) return false;
if (src1->type != GGML_TYPE_F32) return false;
if (!ggml_is_contiguous(src0)) return false;
if (!ggml_is_contiguous(src1)) return false;