Cycle 5 closed: CDEF QPU R5=0.116 ORANGE, opportunistic helper
Phase 4 plan with 3 Phase-5 REDs applied inline: - meta layout: m.z=tmp_off, m.w=dir - sec_shift clamped to >=0 (NEON uqsub semantics) - directions table as const ivec2[14], not OR-packed Phase 6 deliverable: v3d_cdef.comp (387 inst, 2 threads, no spills). 3-way M1 (QPU vs C ref vs NEON) PASS 4096/4096. M2: 0.443 Mblock/s -> R5 = 0.116 ORANGE (predicted 0.02-0.05 RED). M4 same-kernel: NEON-3+QPU 8.46 < NEON-4 alone ~10 (negative). M4 mixed (NEON-3 MC + QPU CDEF): CPU 34.17 Mblock/s MC, QPU 0.42 Mblock/s CDEF helper. CPU side higher than the Issue 003 NEON-fallback proxy suggested - cross-substrate contention is gentler than same-side NEON contention. Verdict: CDEF stays on CPU; QPU dispatch path exists for opportunistic use. Deployment recipe table updated for all 5 cycles. Phase 9 lessons: linear extrapolation across cycles is too pessimistic; CDEF is bandwidth-bound on NEON despite high per-block ns; real-substrate-cross contention < NEON-proxy contention. - src/v3d_cdef.comp: cycle 5 QPU shader - tests/bench_v3d_cdef.c: 3-way M1, M2 bench - tests/bench_concurrent_mixed.c: K_CDEF on both sides - tests/cdef_ref.c + bench_neon_cdef.c: sec_shift clamp + expanded damping range to exercise the edge case - CMakeLists.txt: v3d_cdef.spv + bench_v3d_cdef wiring - docs/k5_cdef_phase4.md updated with Phase 5 review applied - docs/k5_cdef_phase7.md: closure doc with full verdict matrix Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,31 @@ static void neon_run_lpf(uint64_t *seed, uint64_t *out_done, int wd_8) {
|
||||
free(master); free(work); free(Es); free(Is); free(Hs);
|
||||
}
|
||||
|
||||
static void neon_run_cdef(uint64_t *seed, uint64_t *out_done) {
|
||||
int n = NEON_BATCH;
|
||||
uint16_t *tmps = malloc((size_t) n * 192 * sizeof(uint16_t));
|
||||
uint8_t *dsts = malloc((size_t) n * 64);
|
||||
int *pris = malloc(n*sizeof(int)), *secs = malloc(n*sizeof(int));
|
||||
int *dirs = malloc(n*sizeof(int)), *damps = malloc(n*sizeof(int));
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < 192; j++) tmps[i*192 + j] = (uint16_t)(xs_step(seed) & 0xff);
|
||||
for (int r = 0; r < 8; r++) for (int c = 0; c < 8; c++)
|
||||
dsts[i*64 + r*8 + c] = (uint8_t) tmps[i*192 + (r+2)*16 + (c+2)];
|
||||
pris[i] = (int)(xs_step(seed) % 7) + 1;
|
||||
secs[i] = (int)(xs_step(seed) % 4) + 1;
|
||||
dirs[i] = (int)(xs_step(seed) & 7);
|
||||
damps[i] = (int)(xs_step(seed) % 6) + 1;
|
||||
}
|
||||
while (!g_stop) {
|
||||
for (int i = 0; i < n; i++)
|
||||
dav1d_cdef_filter8_8bpc_neon(dsts + i*64, 8,
|
||||
tmps + i*192 + (2*16+2),
|
||||
pris[i], secs[i], dirs[i], damps[i], 8, 0);
|
||||
*out_done += n;
|
||||
}
|
||||
free(tmps); free(dsts); free(pris); free(secs); free(dirs); free(damps);
|
||||
}
|
||||
|
||||
static void neon_run_idct(uint64_t *seed, uint64_t *out_done) {
|
||||
int16_t *blocks_master = malloc((size_t) NEON_BATCH * 64 * sizeof(int16_t));
|
||||
int16_t *blocks_work = malloc((size_t) NEON_BATCH * 64 * sizeof(int16_t));
|
||||
@@ -175,6 +200,7 @@ static void *neon_worker(void *p) {
|
||||
case K_LPF4: neon_run_lpf(&seed, &done, 0); break;
|
||||
case K_LPF8: neon_run_lpf(&seed, &done, 1); break;
|
||||
case K_IDCT: neon_run_idct(&seed, &done); break;
|
||||
case K_CDEF: neon_run_cdef(&seed, &done); break;
|
||||
default: fprintf(stderr, "bad NEON kernel\n"); break;
|
||||
}
|
||||
a->elapsed_s = now_s() - t0;
|
||||
@@ -194,8 +220,8 @@ typedef struct {
|
||||
/* Each QPU kernel has its own push-constant layout. */
|
||||
typedef struct { uint32_t n, dst_stride_u8, _pad0, _pad1; } pc_lpf;
|
||||
typedef struct { uint32_t n, dst_stride_u8, src_stride_u8, _pad; } pc_mc;
|
||||
/* IDCT: pc layout in v3d_idct8.comp = (n_blocks, blocks_per_row, dst_stride_u8, _pad) */
|
||||
typedef struct { uint32_t n_blocks, blocks_per_row, dst_stride_u8, _pad; } pc_idct;
|
||||
typedef struct { uint32_t n_blocks, tmp_stride_u16, dst_stride_u8, _pad; } pc_cdef;
|
||||
/* CDEF: not yet — QPU CDEF kernel not implemented. CDEF QPU mode uses
|
||||
* dav1d NEON via a single-thread NEON call on the QPU host core instead.
|
||||
* That's a degenerate "QPU helper" but matches the deferred state of
|
||||
@@ -296,8 +322,16 @@ static void *qpu_real_worker(void *p)
|
||||
case K_IDCT:
|
||||
spv = "v3d_idct8.spv";
|
||||
dst_bytes = (size_t) n_units * 64;
|
||||
src_bytes = (size_t) n_units * 64 * sizeof(int16_t); /* coeffs */
|
||||
meta_bytes = (size_t) n_units * 4 * sizeof(uint32_t); /* per-block pos */
|
||||
src_bytes = (size_t) n_units * 64 * sizeof(int16_t);
|
||||
meta_bytes = (size_t) n_units * 4 * sizeof(uint32_t);
|
||||
has_src = 1;
|
||||
break;
|
||||
case K_CDEF:
|
||||
spv = "v3d_cdef.spv";
|
||||
bpw = 4;
|
||||
dst_bytes = (size_t) n_units * 64;
|
||||
src_bytes = (size_t) n_units * 192 * sizeof(uint16_t);
|
||||
meta_bytes = (size_t) n_units * 4 * sizeof(uint32_t);
|
||||
has_src = 1;
|
||||
break;
|
||||
default:
|
||||
@@ -334,22 +368,37 @@ static void *qpu_real_worker(void *p)
|
||||
((uint8_t *) buf_src.mapped)[i] = (uint8_t)(xs_step(&seed) & 0xff);
|
||||
} else if (a->kernel == K_IDCT) {
|
||||
for (int i = 0; i < n_units; i++) {
|
||||
meta[4*i+0] = (uint32_t)((size_t)i * 64); /* dst_off */
|
||||
meta[4*i+1] = (uint32_t)((i * 64) / 64); /* coeff_off (in blocks) */
|
||||
meta[4*i+2] = 0; /* eob (not used by our shader) */
|
||||
meta[4*i+0] = (uint32_t)((size_t)i * 64);
|
||||
meta[4*i+1] = (uint32_t)((i * 64) / 64);
|
||||
meta[4*i+2] = 0;
|
||||
meta[4*i+3] = 0;
|
||||
}
|
||||
/* Fill coeffs with random VP9-ish values. */
|
||||
int16_t *cf = (int16_t *) buf_src.mapped;
|
||||
size_t n_coefs = src_bytes / sizeof(int16_t);
|
||||
for (size_t i = 0; i < n_coefs; i++)
|
||||
cf[i] = (int16_t)((int)(xs_step(&seed) % 8192) - 4096);
|
||||
} else if (a->kernel == K_CDEF) {
|
||||
uint16_t *tmps = (uint16_t *) buf_src.mapped;
|
||||
for (int i = 0; i < n_units; i++) {
|
||||
uint32_t pri = (uint32_t)((xs_step(&seed) % 7) + 1);
|
||||
uint32_t sec = (uint32_t)((xs_step(&seed) % 4) + 1);
|
||||
uint32_t damping = (uint32_t)((xs_step(&seed) % 6) + 1);
|
||||
meta[4*i+0] = (uint32_t)((size_t)i * 64);
|
||||
meta[4*i+1] = pri | (sec << 8) | (damping << 16);
|
||||
meta[4*i+2] = (uint32_t)((size_t)i * 192 + (2*16 + 2));
|
||||
meta[4*i+3] = (uint32_t)(xs_step(&seed) & 7);
|
||||
for (int j = 0; j < 192; j++)
|
||||
tmps[(size_t)i * 192 + j] = (uint16_t)(xs_step(&seed) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < dst_bytes; i++)
|
||||
((uint8_t *) buf_dst.mapped)[i] = (uint8_t)(xs_step(&seed) & 0xff);
|
||||
}
|
||||
|
||||
v3d_pipeline pipe = {0};
|
||||
int n_ssbos = has_src ? 3 : 2;
|
||||
size_t pc_size = (a->kernel == K_MC) ? sizeof(pc_mc) :
|
||||
(a->kernel == K_IDCT) ? sizeof(pc_idct) : sizeof(pc_lpf);
|
||||
(a->kernel == K_IDCT) ? sizeof(pc_idct) :
|
||||
(a->kernel == K_CDEF) ? sizeof(pc_cdef) : sizeof(pc_lpf);
|
||||
v3d_runner_create_pipeline(r, spv, n_ssbos, pc_size, &pipe);
|
||||
|
||||
v3d_buffer bind_bufs[3];
|
||||
@@ -359,13 +408,15 @@ static void *qpu_real_worker(void *p)
|
||||
v3d_runner_bind_buffers(r, &pipe, bind_bufs, n_ssbos);
|
||||
|
||||
uint32_t gc = (uint32_t)((n_units + bpw - 1) / bpw);
|
||||
union { pc_lpf lpf; pc_mc mc; pc_idct idct; } pc = {0};
|
||||
union { pc_lpf lpf; pc_mc mc; pc_idct idct; pc_cdef cdef; } pc = {0};
|
||||
if (a->kernel == K_LPF4 || a->kernel == K_LPF8) {
|
||||
pc.lpf = (pc_lpf){ .n = n_units, .dst_stride_u8 = 8 };
|
||||
} else if (a->kernel == K_MC) {
|
||||
pc.mc = (pc_mc){ .n = n_units, .dst_stride_u8 = 8, .src_stride_u8 = 16 };
|
||||
} else if (a->kernel == K_IDCT) {
|
||||
pc.idct = (pc_idct){ .n_blocks = n_units, .blocks_per_row = 16, .dst_stride_u8 = 128 };
|
||||
} else if (a->kernel == K_CDEF) {
|
||||
pc.cdef = (pc_cdef){ .n_blocks = n_units, .tmp_stride_u16 = 16, .dst_stride_u8 = 8 };
|
||||
}
|
||||
|
||||
VkCommandBuffer cb = v3d_runner_alloc_cmdbuf(r);
|
||||
@@ -451,10 +502,10 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
/* CDEF on QPU side currently uses dav1d NEON fallback (cycle 5
|
||||
* Phase 6 not yet implemented). Real QPU CDEF would replace
|
||||
* qpu_cdef_neon_fallback with qpu_real_worker. */
|
||||
int use_neon_fallback_for_cdef = (qpu_k == K_CDEF);
|
||||
/* Cycle 5 Phase 6 landed — v3d_cdef.spv is M1-PASS. Use real
|
||||
* QPU dispatch for CDEF too. The NEON-fallback worker remains
|
||||
* compiled but is unselected. */
|
||||
int use_neon_fallback_for_cdef = 0;
|
||||
|
||||
int barrier_count = n_neon + 1 /* QPU */ + 1 /* timer */ + 1 /* main */;
|
||||
printf("=== Issue 003 mixed-kernel M4 bench ===\n");
|
||||
|
||||
Reference in New Issue
Block a user