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:
2026-05-18 13:52:46 +00:00
parent 1740e7c165
commit 5223d3cb3f
8 changed files with 849 additions and 36 deletions
+64 -13
View File
@@ -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");
+7 -2
View File
@@ -79,12 +79,17 @@ static void gen_filter_params(int *pri, int *sec, int *dir, int *damping)
* pri_strength: 1..7 (non-zero for combined path)
* sec_strength: 1..4
* dir: 0..7
* damping: 3..6
* damping: 1..6 — extended down to 1 (was 3..6) per
* cycle 5 phase 5 RED-2: include cases where
* sec_shift = damping - ulog2(sec) goes negative
* (e.g. damping=1, sec=4 → sec_shift = -1).
* Both NEON (uqsub) and C ref (now max(0,...))
* saturate to 0 here; the bench should exercise it.
*/
*pri = (int)(xs() % 7) + 1;
*sec = (int)(xs() % 4) + 1;
*dir = (int)(xs() & 7);
*damping = (int)(xs() % 4) + 3;
*damping = (int)(xs() % 6) + 1;
}
static double now_seconds(void)
+332
View File
@@ -0,0 +1,332 @@
/*
* Cycle 5 Phase 6 QPU bench for AV1 CDEF primary+secondary 8x8
* luma filter on V3D 7.1.
*
* Reports:
* M1: 3-way bit-exact (QPU vs NEON vs C reference) per Phase 5
* YELLOW-1.
* M2: QPU sustained Mblock/s over K dispatched batches
*
* License: BSD-2-Clause; links dav1d 1.4.3 NEON snapshot.
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <getopt.h>
#include <vulkan/vulkan.h>
#include "v3d_runner.h"
extern void daedalus_cdef_filter_8x8_pri_sec_ref(
uint8_t *dst, ptrdiff_t dst_stride,
const uint16_t *tmp,
int pri_strength, int sec_strength,
int dir, int damping, int h);
extern void dav1d_cdef_filter8_8bpc_neon(
uint8_t *dst, ptrdiff_t dst_stride,
const uint16_t *tmp,
int pri_strength, int sec_strength,
int dir, int damping, int h, size_t edges);
#define TMP_W 16
#define TMP_H 12
#define TMP_INTS (TMP_W * TMP_H) /* 192 */
#define DST_W 8
#define DST_H 8
#define DST_BYTES (DST_H * DST_W) /* 64 */
#define BLOCK_ORIGIN_U16 (2 * TMP_W + 2) /* 34 */
static uint64_t xs_state;
static inline uint64_t xs(void) {
uint64_t x = xs_state;
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
return xs_state = x;
}
static void gen_tmp(uint16_t *tmp)
{
for (int i = 0; i < TMP_INTS; i++)
tmp[i] = (uint16_t)(xs() & 0xff);
}
static void tmp_center_to_dst(uint8_t *dst, const uint16_t *tmp)
{
for (int r = 0; r < 8; r++)
for (int c = 0; c < 8; c++)
dst[r * 8 + c] = (uint8_t) tmp[(r + 2) * TMP_W + (c + 2)];
}
static void gen_filter_params(int *pri, int *sec, int *dir, int *damping)
{
*pri = (int)(xs() % 7) + 1;
*sec = (int)(xs() % 4) + 1;
*dir = (int)(xs() & 7);
*damping = (int)(xs() % 6) + 1; /* includes negative-sec_shift cases */
}
static double now_seconds(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return ts.tv_sec + ts.tv_nsec * 1e-9;
}
typedef struct {
uint32_t n_blocks;
uint32_t tmp_stride_u16;
uint32_t dst_stride_u8;
uint32_t _pad;
} push_consts;
int main(int argc, char **argv)
{
int n_blocks = 16384;
int iters = 200;
int verify_only = 0;
uint64_t seed = 0;
const char *spv_path = "v3d_cdef.spv";
static struct option opts[] = {
{"blocks", required_argument, 0, 'b'},
{"iters", required_argument, 0, 'i'},
{"seed", required_argument, 0, 's'},
{"spv", required_argument, 0, 'S'},
{"verify-only", no_argument, 0, 'V'},
{0,0,0,0}
};
for (int c; (c = getopt_long(argc, argv, "b:i:s:S:V", opts, 0)) != -1;) {
switch (c) {
case 'b': n_blocks = atoi(optarg); break;
case 'i': iters = atoi(optarg); break;
case 's': seed = strtoull(optarg, 0, 0); break;
case 'S': spv_path = optarg; break;
case 'V': verify_only = 1; break;
default: return 2;
}
}
xs_state = seed ? seed : 0xc0defacedcafebebULL;
v3d_runner *r = v3d_runner_create();
if (!r) { fprintf(stderr, "v3d_runner_create failed\n"); return 1; }
printf("=== v3d CDEF bench ===\n");
printf(" device: %s\n", v3d_runner_device_name(r));
printf(" n_blocks: %d iters: %d seed: 0x%016llx\n",
n_blocks, iters, (unsigned long long) (seed ? seed : 0xc0defacedcafebebULL));
size_t meta_bytes = (size_t) n_blocks * 4 * sizeof(uint32_t); /* uvec4 */
size_t dst_bytes = (size_t) n_blocks * DST_BYTES;
size_t tmp_bytes = (size_t) n_blocks * TMP_INTS * sizeof(uint16_t);
v3d_buffer buf_meta = {0}, buf_dst = {0}, buf_tmp = {0};
if (v3d_runner_create_buffer(r, meta_bytes, &buf_meta)) return 1;
if (v3d_runner_create_buffer(r, dst_bytes, &buf_dst)) return 1;
if (v3d_runner_create_buffer(r, tmp_bytes, &buf_tmp)) return 1;
uint8_t *master_dst = malloc(dst_bytes);
uint8_t *expected_c = malloc(dst_bytes);
uint8_t *expected_n = malloc(dst_bytes);
int *pris = malloc(n_blocks * sizeof(int));
int *secs = malloc(n_blocks * sizeof(int));
int *dirs = malloc(n_blocks * sizeof(int));
int *damps = malloc(n_blocks * sizeof(int));
if (!master_dst || !expected_c || !expected_n || !pris || !secs || !dirs || !damps) {
fprintf(stderr, "alloc fail\n"); return 1;
}
/* Generate tmp + params + initial dst (block center extracted). */
uint16_t *tmp_gpu = (uint16_t *) buf_tmp.mapped;
for (int i = 0; i < n_blocks; i++) {
uint16_t *tmp = tmp_gpu + (size_t)i * TMP_INTS;
gen_tmp(tmp);
tmp_center_to_dst(master_dst + (size_t)i * DST_BYTES, tmp);
gen_filter_params(&pris[i], &secs[i], &dirs[i], &damps[i]);
}
/* Compute C-ref and NEON expected outputs (serial, on master_dst). */
memcpy(expected_c, master_dst, dst_bytes);
memcpy(expected_n, master_dst, dst_bytes);
for (int i = 0; i < n_blocks; i++) {
daedalus_cdef_filter_8x8_pri_sec_ref(
expected_c + (size_t)i * DST_BYTES, DST_W,
tmp_gpu + (size_t)i * TMP_INTS,
pris[i], secs[i], dirs[i], damps[i], 8);
dav1d_cdef_filter8_8bpc_neon(
expected_n + (size_t)i * DST_BYTES, DST_W,
tmp_gpu + (size_t)i * TMP_INTS + BLOCK_ORIGIN_U16,
pris[i], secs[i], dirs[i], damps[i], 8, 0);
}
/* Confirm 2-way C vs NEON parity (defence in depth — Phase 3 already
* passed this for 10000 blocks, but n_blocks may be larger here). */
int cn_mis = 0;
for (int i = 0; i < n_blocks; i++) {
if (memcmp(expected_c + (size_t)i * DST_BYTES,
expected_n + (size_t)i * DST_BYTES, DST_BYTES) != 0) cn_mis++;
}
printf(" C ref vs NEON parity check: %d/%d mismatches\n", cn_mis, n_blocks);
if (cn_mis > 0) {
fprintf(stderr, "ERROR: C ref disagrees with NEON before QPU even runs.\n");
return 1;
}
/* Populate meta SSBO (post Phase 5 RED-1 layout). */
uint32_t *meta = (uint32_t *) buf_meta.mapped;
uint32_t dst_stride_u8 = DST_W; /* 8 */
uint32_t tmp_stride_u16 = TMP_W; /* 16 */
for (int i = 0; i < n_blocks; i++) {
uint32_t pri = (uint32_t) pris[i];
uint32_t sec = (uint32_t) secs[i];
uint32_t damping = (uint32_t) damps[i];
meta[4*i + 0] = (uint32_t)((size_t)i * DST_BYTES);
meta[4*i + 1] = pri | (sec << 8) | (damping << 16);
meta[4*i + 2] = (uint32_t)((size_t)i * TMP_INTS + BLOCK_ORIGIN_U16);
meta[4*i + 3] = (uint32_t) dirs[i];
}
/* Pipeline (3 SSBOs). */
v3d_pipeline pipe = {0};
if (v3d_runner_create_pipeline(r, spv_path,
/*n_ssbos=*/3,
/*push_const_size=*/sizeof(push_consts),
&pipe)) return 1;
v3d_buffer bind_bufs[3] = { buf_meta, buf_dst, buf_tmp };
if (v3d_runner_bind_buffers(r, &pipe, bind_bufs, 3)) return 1;
const uint32_t blocks_per_wg = 4;
uint32_t group_count_x = (uint32_t)((n_blocks + blocks_per_wg - 1) / blocks_per_wg);
printf(" dispatch: %u WGs × 256 invocations = %u blocks\n",
group_count_x, group_count_x * blocks_per_wg);
push_consts pc = {
.n_blocks = (uint32_t) n_blocks,
.tmp_stride_u16 = tmp_stride_u16,
.dst_stride_u8 = dst_stride_u8,
._pad = 0,
};
VkCommandBuffer cb = v3d_runner_alloc_cmdbuf(r);
if (cb == VK_NULL_HANDLE) return 1;
VkCommandBufferBeginInfo cbbi = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
vkBeginCommandBuffer(cb, &cbbi);
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pipe.pipeline);
vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_COMPUTE,
pipe.layout, 0, 1, &pipe.desc_set, 0, NULL);
vkCmdPushConstants(cb, pipe.layout, VK_SHADER_STAGE_COMPUTE_BIT,
0, sizeof(pc), &pc);
vkCmdDispatch(cb, group_count_x, 1, 1);
vkEndCommandBuffer(cb);
/* --- M1: QPU vs C-ref vs NEON 3-way --- */
printf("\n=== M1₅: QPU vs C-ref vs NEON 3-way ===\n");
memcpy(buf_dst.mapped, master_dst, dst_bytes);
if (v3d_runner_submit_wait(r, cb)) return 1;
int qc_mismatches = 0, qn_mismatches = 0;
int prints = 0;
for (int i = 0; i < n_blocks; i++) {
const uint8_t *q = (uint8_t *) buf_dst.mapped + (size_t)i * DST_BYTES;
const uint8_t *c = expected_c + (size_t)i * DST_BYTES;
const uint8_t *n = expected_n + (size_t)i * DST_BYTES;
int qc = memcmp(q, c, DST_BYTES);
int qn = memcmp(q, n, DST_BYTES);
if (qc) qc_mismatches++;
if (qn) qn_mismatches++;
if ((qc || qn) && prints < 3) {
fprintf(stderr, "MISMATCH block %d (pri=%d sec=%d dir=%d damp=%d):\n",
i, pris[i], secs[i], dirs[i], damps[i]);
fprintf(stderr, " C ref:");
for (int r0 = 0; r0 < 8; r0++) {
fprintf(stderr, "\n r%d ", r0);
for (int c0 = 0; c0 < 8; c0++) fprintf(stderr, "%3u ", c[r0*8+c0]);
}
fprintf(stderr, "\n QPU:");
for (int r0 = 0; r0 < 8; r0++) {
fprintf(stderr, "\n r%d ", r0);
for (int c0 = 0; c0 < 8; c0++) fprintf(stderr, "%3u ", q[r0*8+c0]);
}
fprintf(stderr, "\n");
prints++;
}
}
printf(" QPU vs C ref: %d / %d blocks bit-exact (%.4f%%)\n",
n_blocks - qc_mismatches, n_blocks,
100.0 * (n_blocks - qc_mismatches) / n_blocks);
printf(" QPU vs NEON: %d / %d blocks bit-exact (%.4f%%)\n",
n_blocks - qn_mismatches, n_blocks,
100.0 * (n_blocks - qn_mismatches) / n_blocks);
if (qc_mismatches > 0 || qn_mismatches > 0) {
fprintf(stderr, "REFUSING to measure throughput on a broken kernel.\n");
return 1;
}
if (verify_only) {
v3d_runner_destroy_pipeline(r, &pipe);
v3d_runner_destroy_buffer(r, &buf_tmp);
v3d_runner_destroy_buffer(r, &buf_dst);
v3d_runner_destroy_buffer(r, &buf_meta);
v3d_runner_destroy(r);
return 0;
}
/* --- M2: throughput --- */
printf("\n=== M2₅: QPU throughput ===\n");
for (int i = 0; i < 5; i++) {
memcpy(buf_dst.mapped, master_dst, dst_bytes);
if (v3d_runner_submit_wait(r, cb)) return 1;
}
double t0 = now_seconds();
for (int i = 0; i < iters; i++) {
memcpy(buf_dst.mapped, master_dst, dst_bytes);
if (v3d_runner_submit_wait(r, cb)) return 1;
}
double t1 = now_seconds();
double s0 = now_seconds();
for (int i = 0; i < iters; i++) memcpy(buf_dst.mapped, master_dst, dst_bytes);
double s1 = now_seconds();
double kernel_seconds = (t1 - t0) - (s1 - s0);
double total_blocks = (double) n_blocks * iters;
double mbps = total_blocks / kernel_seconds / 1e6;
printf(" blocks/dispatch: %d\n", n_blocks);
printf(" iters: %d\n", iters);
printf(" total blocks: %.0f\n", total_blocks);
printf(" elapsed (kernel)=%.6f s (setup-subtracted)\n", kernel_seconds);
printf(" elapsed (setup) =%.6f s\n", s1 - s0);
printf(" M2₅ throughput = %.3f Mblock/s\n", mbps);
printf(" per-block = %.1f ns\n", kernel_seconds / total_blocks * 1e9);
printf(" per-dispatch = %.1f us\n", kernel_seconds / iters * 1e6);
double M3_5 = 3.809;
double R5 = mbps / M3_5;
printf("\n Cycle 5 NEON M3₅ = %.3f Mblock/s\n", M3_5);
printf(" R₅ = M2₅/M3₅ = %.3f\n", R5);
if (R5 >= 1.0) printf(" decision band = GREEN: QPU beats NEON in isolation\n");
else if (R5 >= 0.5) printf(" decision band = YELLOW: M4 decides\n");
else if (R5 >= 0.1) printf(" decision band = ORANGE: M4 may still rescue\n");
else printf(" decision band = RED: structural mismatch (predicted)\n");
/* 30fps@1080p floor: 32400 blocks/frame × 30 fps = 0.972 Mblock/s */
double floor_rate = 0.972;
printf(" 30fps@1080p floor: %.2fx margin (isolation)\n", mbps / floor_rate);
v3d_runner_destroy_pipeline(r, &pipe);
v3d_runner_destroy_buffer(r, &buf_tmp);
v3d_runner_destroy_buffer(r, &buf_dst);
v3d_runner_destroy_buffer(r, &buf_meta);
v3d_runner_destroy(r);
free(master_dst); free(expected_c); free(expected_n);
free(pris); free(secs); free(dirs); free(damps);
return 0;
}
+4 -1
View File
@@ -98,7 +98,10 @@ void daedalus_cdef_filter_8x8_pri_sec_ref(
{
const int pri_tap = 4 - (pri_strength & 1);
const int pri_shift = imax(0, damping - ulog2((unsigned) pri_strength));
const int sec_shift = damping - ulog2((unsigned) sec_strength);
/* Cycle 5 phase 5 RED-2: NEON `uqsub` saturates to 0. Mirror it
* here so the C ref is bit-exact against NEON for damping-light
* cases (which the original bench param gen didn't exercise). */
const int sec_shift = imax(0, damping - ulog2((unsigned) sec_strength));
/* Walk into the center 8x8 region of the 12×16 padded buffer. */
tmp = tmp + 2 * TMP_STRIDE + 2;