ggml-rocket: CBUF weight-reuse via multi-task job chaining

Standalone experiment (Fable-directed): chaining a column's 32 row-tiles into
ONE drm_rocket_job (task_count>1) so only task 0 fetches the column's weights
DDR->CBUF and tasks 1..k reuse the SRAM-resident weights (CNA_CBUF_CON0_WEIGHT_
REUSE, gated on task_num>0) gives **4.33x** single-core (34->148 GFLOPS) and is
**bit-exact** vs the single-task path (0/65536 bytes differ). Confirms Fable's
diagnosis that the tiled matmul is DDR->CBUF weight-DMA bound (~130us/tile vs
~9us ideal-compute), not compute bound.

Integrated into rkt_npu_matmul: tiles are column-major, so a column's row tiles
are contiguous -> emit them as one multi-task job (rkt_g_task_num = 0..k-1,
chunked at RKT_MAXTASKS=32). Correctness re-verified (kscan all shapes, gemma4
pp512 clean). Neutral at current coverage (only the 2 largest bf16 ops offload,
already NPU-bound there); the 4.3x pays off once coverage is high (needs the
prepack buffer-type) and stacks with multi-fd 3-core.

NOTE: matching vendor (65 t/s) still needs the remaining pieces — Fable proved
single-fd caps at 2 NPU cores (DRM sched tie-break; core2 gets 0.2% of jobs) so
3 independent fds are required for 3-core (33/33/33, ~6.4x aggregate proven),
plus a prepack weight buffer-type for q8_0 coverage (CPU_REPACK blocks it) under
a hard 4GiB/fd IOVA budget (repacked E2B ~10.6GB -> must 3-way size-partition).

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 13:13:33 +02:00
parent 367143edf3
commit 412cba2601
2 changed files with 91 additions and 68 deletions
+3 -1
View File
@@ -83,6 +83,8 @@ static unsigned calc_weights_banks(const struct op *o)
return banks;
}
int rkt_g_task_num = 0; /* experiment: CBUF weight-reuse multi-task chaining */
int rkt_build_matmul_regcmd_scaled(uint64_t *out, int out_capacity,
uint32_t M, uint32_t N, uint32_t K,
uint64_t input_dma, uint64_t weights_dma,
@@ -208,7 +210,7 @@ int rkt_build_matmul_regcmd_scaled(uint64_t *out, int out_capacity,
c.reuse_weights_cbuf = reuse_weights_cbuf;
c.addition_input = o.addition_input;
c.add_tensor = o.add_tensor;
c.task_num = 0;
c.task_num = rkt_g_task_num;
/* ---- CORE/DPU/PC params ---- */
struct coredpu_params d;
+88 -67
View File
@@ -38,6 +38,8 @@ __attribute__((destructor)) static void rkt_prof_dump(void){
g_setup_ns/1e9,g_submit_ns/1e9,g_prep_ns/1e9,g_free_ns/1e9);
}
extern int rkt_g_task_num;
int rkt_build_matmul_regcmd_scaled(uint64_t *out, int out_capacity,
uint32_t M, uint32_t N, uint32_t K,
uint64_t input_dma, uint64_t weights_dma,
@@ -89,7 +91,9 @@ int rkt_npu_matmul(int fd, const uint8_t *X, const uint8_t *Wc,
{
int ret = -1;
int32_t *bias0 = NULL;
struct rkt_bo in = {0}, w = {0}, b = {0}, o = {0}, reg = {0};
#define RKT_MAXTASKS 32
struct rkt_bo w = {0}, b = {0};
struct rkt_bo rin[RKT_MAXTASKS] = {{0}}, ro[RKT_MAXTASKS] = {{0}}, rreg[RKT_MAXTASKS] = {{0}};
uint8_t *got = NULL;
if (!bias) {
bias0 = calloc(N, sizeof(int32_t));
@@ -118,84 +122,101 @@ int rkt_npu_matmul(int fd, const uint8_t *X, const uint8_t *Wc,
if (nt <= 0)
goto out;
/* Per-call BO pool sized for the largest tile: reuse across every tile.
* Thousands of CREATE_BO/mmap/GEM_CLOSE per call were the dominant cost
* (NPU compute is a small fraction of per-tile buffer churn). */
if (bo_alloc(fd, &in, rkt_raw_input_size(1, tile_m, K)) ||
bo_alloc(fd, &w, rkt_packed_weights_size(1, 1, K, tile_n)) ||
bo_alloc(fd, &b, tile_n * sizeof(int32_t)) ||
bo_alloc(fd, &o, rkt_raw_output_size(1, tile_m, tile_n)) ||
bo_alloc(fd, &reg, 0x1000))
/* CBUF weight-reuse: chain a column's row tiles into ONE multi-task job so
* only task 0 fetches the column's weights DDR->CBUF and tasks 1..k reuse
* the SRAM-resident weights (CNA_CBUF_CON0_WEIGHT_REUSE). ~4x fewer weight
* DMAs; tiles are column-major so a column's row tiles are contiguous. */
if (bo_alloc(fd, &w, rkt_packed_weights_size(1, 1, K, tile_n)) ||
bo_alloc(fd, &b, tile_n * sizeof(int32_t)))
goto out;
for (int s2 = 0; s2 < RKT_MAXTASKS; s2++) {
if (bo_alloc(fd, &rin[s2], rkt_raw_input_size(1, tile_m, K)) ||
bo_alloc(fd, &ro[s2], rkt_raw_output_size(1, tile_m, tile_n)) ||
bo_alloc(fd, &rreg[s2], 0x1000))
goto out;
}
got = malloc((size_t)tile_m * tile_n);
if (!got)
goto out;
{
uint32_t ih[] = { in.h, w.h, b.h, reg.h }, oh[] = { o.h };
unsigned last_cc = (unsigned)-1;
for (int t = 0; t < nt; t++) {
unsigned r = tiles[t].row, cc = tiles[t].col;
unsigned m = tiles[t].m, n = tiles[t].n;
{
int t = 0;
while (t < nt) {
unsigned cc = tiles[t].col, n = tiles[t].n;
uint64_t _t = rkt_prof_on()?rkt_now_ns():0;
/* weights + bias depend only on the column tile: pack+write once,
* reuse the (device-resident) weight BO across all row tiles. */
if (cc != last_cc) {
for (unsigned j = 0; j < n; j++)
memcpy(Wt + (size_t)j * K, Wc + (size_t)(cc + j) * K, K);
rkt_pack_weights(Wt, 1, 1, K, n, wzp, wpk);
bo_write(fd, &w, wpk, rkt_packed_weights_size(1, 1, K, n));
rkt_compute_biases(Wt, bias + cc, 1, 1, K, n, wzp, izp, bpk);
bo_write(fd, &b, bpk, n * sizeof(int32_t));
last_cc = cc;
for (unsigned j = 0; j < n; j++)
memcpy(Wt + (size_t)j * K, Wc + (size_t)(cc + j) * K, K);
rkt_pack_weights(Wt, 1, 1, K, n, wzp, wpk);
bo_write(fd, &w, wpk, rkt_packed_weights_size(1, 1, K, n));
rkt_compute_biases(Wt, bias + cc, 1, 1, K, n, wzp, izp, bpk);
bo_write(fd, &b, bpk, n * sizeof(int32_t));
/* chunk this column's row tiles into multi-task jobs of <= MAXTASKS */
while (t < nt && tiles[t].col == cc) {
struct drm_rocket_task tasks[RKT_MAXTASKS];
uint32_t ih[2 + 2*RKT_MAXTASKS], oh[RKT_MAXTASKS];
struct { unsigned r, m, n; } meta[RKT_MAXTASKS];
int cnt = 0, ni = 0, no = 0;
ih[ni++] = w.h; ih[ni++] = b.h;
while (t < nt && tiles[t].col == cc && cnt < RKT_MAXTASKS) {
unsigned r = tiles[t].row, m = tiles[t].m;
for (unsigned y = 0; y < m; y++)
memcpy(Xt + (size_t)y * K, X + (size_t)(r + y) * K, K);
rkt_pack_input(Xt, 1, m, K, izp, ipk);
bo_write(fd, &rin[cnt], ipk, rkt_raw_input_size(1, m, K));
bo_write(fd, &ro[cnt], NULL, rkt_raw_output_size(1, m, n));
rkt_g_task_num = cnt; /* task 0 loads weights, 1+ reuse CBUF */
int nw = rkt_build_matmul_regcmd_scaled(
rc, 4096, m, n, K, rin[cnt].dma, w.dma, ro[cnt].dma,
izp, wzp, ozp, in_scale, wt_scale,
out_scales ? out_scales[cc] : out_scale, b.dma);
if (nw < 0) { rkt_g_task_num = 0; goto out; }
bo_write(fd, &rreg[cnt], rc, (unsigned)nw * sizeof(uint64_t));
tasks[cnt].regcmd = (uint32_t)rreg[cnt].dma;
tasks[cnt].regcmd_count = (uint32_t)nw;
ih[ni++] = rin[cnt].h; ih[ni++] = rreg[cnt].h; oh[no++] = ro[cnt].h;
meta[cnt].r = r; meta[cnt].m = m; meta[cnt].n = n;
cnt++; t++;
}
rkt_g_task_num = 0;
struct drm_rocket_job job;
memset(&job, 0, sizeof job);
job.tasks = (uintptr_t)tasks;
job.task_count = (uint32_t)cnt;
job.task_struct_size = sizeof(struct drm_rocket_task);
job.in_bo_handles = (uintptr_t)ih;
job.in_bo_handle_count = (uint32_t)ni;
job.out_bo_handles = (uintptr_t)oh;
job.out_bo_handle_count = (uint32_t)no;
if (rkt_prof_on()){g_setup_ns+=rkt_now_ns()-_t; _t=rkt_now_ns();}
int sr = rocket_submit(fd, &job, 1);
if (rkt_prof_on()){g_submit_ns+=rkt_now_ns()-_t; _t=rkt_now_ns();}
if (sr) goto out;
for (int i = 0; i < cnt; i++) {
if (rocket_prep_bo(fd, ro[i].h, rkt_deadline_ns(6000000000LL)))
goto out;
rkt_unpack_output(ro[i].map, 1, meta[i].m, meta[i].n, got);
for (unsigned y = 0; y < meta[i].m; y++)
memcpy(Y + (size_t)(meta[i].r + y) * N + cc,
got + (size_t)y * meta[i].n, meta[i].n);
}
if (rkt_prof_on()){g_prep_ns+=rkt_now_ns()-_t; g_tiles += cnt;}
}
for (unsigned y = 0; y < m; y++)
memcpy(Xt + (size_t)y * K, X + (size_t)(r + y) * K, K);
rkt_pack_input(Xt, 1, m, K, izp, ipk);
bo_write(fd, &in, ipk, rkt_raw_input_size(1, m, K));
bo_write(fd, &o, NULL, rkt_raw_output_size(1, m, n));
int nw = rkt_build_matmul_regcmd_scaled(
rc, 4096, m, n, K, in.dma, w.dma, o.dma, izp, wzp, ozp,
in_scale, wt_scale, out_scales ? out_scales[cc] : out_scale, b.dma);
if (nw < 0)
goto out;
bo_write(fd, &reg, rc, (unsigned)nw * sizeof(uint64_t));
struct drm_rocket_task task = { .regcmd = (uint32_t)reg.dma,
.regcmd_count = (uint32_t)nw };
struct drm_rocket_job job;
memset(&job, 0, sizeof job);
job.tasks = (uintptr_t)&task;
job.task_count = 1;
job.task_struct_size = sizeof(struct drm_rocket_task);
job.in_bo_handles = (uintptr_t)ih;
job.in_bo_handle_count = 4;
job.out_bo_handles = (uintptr_t)oh;
job.out_bo_handle_count = 1;
if(rkt_prof_on()){g_setup_ns+=rkt_now_ns()-_t; _t=rkt_now_ns();}
int sr = rocket_submit(fd, &job, 1);
if(rkt_prof_on()){g_submit_ns+=rkt_now_ns()-_t; _t=rkt_now_ns();}
int wr = sr ? -1 : rocket_prep_bo(fd, o.h, rkt_deadline_ns(6000000000LL));
if(rkt_prof_on()){g_prep_ns+=rkt_now_ns()-_t; _t=rkt_now_ns();}
if (!sr && !wr) {
rkt_unpack_output(o.map, 1, m, n, got);
for (unsigned y = 0; y < m; y++)
memcpy(Y + (size_t)(r + y) * N + cc, got + (size_t)y * n, n);
}
if(rkt_prof_on()){g_free_ns+=rkt_now_ns()-_t; g_tiles++;}
if (sr || wr)
goto out;
}
}
if(rkt_prof_on()) g_calls++;
if (rkt_prof_on()) g_calls++;
ret = 0;
out:
bo_free(fd, &in); bo_free(fd, &w); bo_free(fd, &b);
bo_free(fd, &o); bo_free(fd, &reg);
bo_free(fd, &w); bo_free(fd, &b);
for (int s2 = 0; s2 < RKT_MAXTASKS; s2++) {
bo_free(fd, &rin[s2]); bo_free(fd, &ro[s2]); bo_free(fd, &rreg[s2]);
}
free(got);
free(Xt); free(Wt); free(ipk); free(wpk); free(bpk);
free(tiles); free(rc); free(bias0);