20b59cd6a5
CDEF is the most compute-intensive kernel measured so far —
254.9 ns/block (2x IDCT, 5x MC). 30fps@1080p floor margin: 4x
even on single NEON core in isolation.
M3 captured cleanly via dav1d_cdef_filter8_8bpc_neon. M1 bit-exact
gate failing due to tmp-layout mismatch between my standalone C
reference and dav1d's NEON expectation. The smoking gun: NEON output
appears at (+2 rows, -2 cols) shifted positions vs C ref output —
suggests NEON's padding-function output has a different convention
than my manual tmp construction.
Untangled in setup work:
- dav1d has TWO directions tables: stride-12 in src/tables.c
(C-side), stride-16 in src/arm/64/cdef_tmpl.S (NEON-side).
Initially vendored the C-side; should have used the NEON-side.
- dav1d's NEON expects tmp built by dav1d_cdef_padding8_8bpc_neon
(a separate function with its own conventions), not the C-side
padding() function from cdef_tmpl.c.
- Updated cdef_ref.c to use NEON-layout (stride 16) with table
transcribed from cdef_tmpl.S. Algorithm matches — but bench's
manual tmp construction doesn't match what NEON expects.
Resolution paths for next session (documented in
docs/k5_cdef_phase3_partial.md §'Resolution paths'):
1. Use dav1d_cdef_padding8_8bpc_neon to construct tmp (simplest)
2. Vendor dav1d's full C reference (most rigorous)
3. Reverse-engineer dav1d's padding output layout (hackiest)
Predicted R5 if/when QPU shader implemented: 0.02-0.05 (RED).
CDEF likely stays on CPU per cycle 3 lesson 7 (compute-bound
kernels don't benefit from QPU offload). 30fps floor still
passes regardless.
New artifacts:
- external/dav1d-snapshot/src/arm/64/cdef_tmpl.S (additional vendored)
- external/dav1d-snapshot/config.h — 14-define asm preamble shim
- tests/cdef_ref.c — standalone C ref (algorithmically correct,
layout mismatch with NEON known)
- tests/bench_neon_cdef.c — bench (M1 made warning, M3 captured)
- docs/k5_cdef_phase3_partial.md — phase 3 partial closure +
resumption checklist
dav1d snapshot in PROVENANCE.md should be updated next session
with the new cdef_tmpl.S entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
149 lines
5.9 KiB
Markdown
149 lines
5.9 KiB
Markdown
---
|
||
cycle: 5
|
||
phase: 3 (partial — M3 captured, M1 deferred)
|
||
status: in_progress (M1 known-issue, Phase 4+ deferred)
|
||
date_opened: 2026-05-18
|
||
date_partial_close: 2026-05-18
|
||
parent: k5_cdef_phase1_2.md
|
||
---
|
||
|
||
# Cycle 5, Phase 3 (partial) — CDEF NEON baseline
|
||
|
||
Cycle 5 Phase 3 captured **M3₅ throughput** but **M1 bit-exact gate
|
||
deferred** to next session due to a tmp-layout mismatch between the
|
||
standalone C reference and dav1d's NEON expectation.
|
||
|
||
## M3₅ NEON throughput (captured)
|
||
|
||
```
|
||
=== M3₅ NEON throughput ===
|
||
blocks/batch: 65536
|
||
batches done: 279
|
||
total blocks: 18 284 544
|
||
elapsed (kernel)=4.661 s
|
||
throughput = 3.923 Mblock/s
|
||
per-block = 254.9 ns
|
||
equiv 1080p = 121.1 FPS (32 400 blocks/frame)
|
||
```
|
||
|
||
**Per-block 254 ns** — CDEF is the most compute-intensive kernel
|
||
measured so far:
|
||
|
||
| | per-block ns | relative |
|
||
|---|---|---|
|
||
| IDCT 8×8 (k1) | 122 | 1.0× |
|
||
| LPF wd=4 (k2) | 20.7 | 0.17× |
|
||
| MC 8h (k3) | 47.6 | 0.39× |
|
||
| LPF wd=8 (k4) | 19.1 | 0.16× |
|
||
| **CDEF (k5)** | **254.9** | **2.09×** |
|
||
|
||
30fps@1080p floor margin: **4×** isolation (32 400 × 30 fps ÷ 1e6 =
|
||
0.972 Mblock/s required; 3.923 / 0.972 = 4.04). NEON CDEF on a
|
||
single CPU core comfortably exceeds the user-facing test alone.
|
||
|
||
## M1 known-issue (deferred to next session)
|
||
|
||
The bit-exact gate against my standalone C reference fails. The
|
||
output structure (NEON vs C ref) shows the NEON producing
|
||
algorithmically-correct-looking pixel values, but at a SHIFTED
|
||
(row, col) offset within dst. Trace evidence:
|
||
|
||
> neon row 5, cols 2-7 = `90 213 247 143 95 76`
|
||
> C ref row 3, cols 0-5 = `90 213 247 143 95 76`
|
||
|
||
— same 6-byte sequence at an offset of (+2 rows, -2 cols) =
|
||
(+2×8 + (-2)) = +14 byte stride mismatch. The smoking gun is that
|
||
dav1d's NEON expects tmp built by a specific
|
||
`dav1d_cdef_padding8_8bpc_neon` routine (different from the C-side
|
||
`padding()` function), and my manual tmp construction doesn't match
|
||
that convention.
|
||
|
||
**Resolution paths** (next session):
|
||
1. **Call dav1d's NEON padding function** to construct tmp from
|
||
dst+left+top+bottom random inputs. Then the filter reads it
|
||
with the right layout. Adds another extern symbol to bind.
|
||
2. **Vendor `dav1d_cdef_filter_block_8x8_c` from dav1d's C-side**
|
||
(with templated headers shimmed). Compare NEON output against
|
||
dav1d's *own* C, not my standalone transcription. Eliminates the
|
||
layout-shim ambiguity entirely.
|
||
3. Inspect `dav1d_cdef_padding8_8bpc_neon` output for one block,
|
||
reverse-engineer the layout, update standalone C ref to match.
|
||
|
||
Path 1 is probably simplest. The padding function signature
|
||
(inferred from cdef.S `padding_func` macro):
|
||
```
|
||
void cdef_padding8_8bpc_neon(uint16_t *tmp, const uint8_t *src,
|
||
ptrdiff_t src_stride,
|
||
const uint8_t (*left)[2],
|
||
const uint8_t *top, const uint8_t *bottom,
|
||
int h, size_t edges);
|
||
```
|
||
|
||
Phase 3 closure requires M1 bit-exact verified.
|
||
|
||
## Phase 4-7 deferred
|
||
|
||
Without M1 verified, can't safely build the QPU shader (would have
|
||
no correctness gate against the NEON path either, and we'd be
|
||
chasing two layout issues simultaneously).
|
||
|
||
**Predicted R₅** (extrapolating from cycle 3 MC):
|
||
- CDEF is ~5× heavier per-block than MC on NEON (254 vs 47 ns)
|
||
- NEON ~5× advantage → QPU likely ~25× behind
|
||
- R₅ isolation estimate: **0.02-0.05 (deep RED)**
|
||
- M4₅ mixed: very likely negative (deeper than cycle 3 MC's -19.5%)
|
||
- 30fps floor: still PASS on isolation+mixed since NEON 4-core
|
||
baseline likely 12+ Mblock/s, comfortably above 0.972
|
||
|
||
**Deployment recommendation** (provisional, pending Phase 4-7):
|
||
CDEF stays on CPU. Same verdict as MC. **All compute-bound kernels
|
||
stay on CPU; all bandwidth-bound (IDCT/LPF) kernels offload to QPU.**
|
||
This is starting to look like a clean classification rule across all
|
||
cycles.
|
||
|
||
## Phase 9 lessons (provisional)
|
||
|
||
1. **Vendoring from a SECOND upstream (dav1d after FFmpeg) added
|
||
non-trivial layout-convention friction.** Different projects make
|
||
different optimisation tradeoffs (dav1d NEON uses stride-16 tmp
|
||
for vector-load alignment; dav1d C uses stride-12 because it
|
||
doesn't matter for scalar code). Standalone C ref had to be
|
||
re-fit to match NEON layout, not just transcribe C.
|
||
|
||
2. **Two different `dav1d_cdef_directions` tables in dav1d**:
|
||
stride-12 in `src/tables.c` (used by C path), stride-16 in
|
||
`src/arm/64/cdef_tmpl.S` (used by NEON path). I initially vendored
|
||
the C-side table; should have used the NEON-side embedded version
|
||
for matching against NEON.
|
||
|
||
3. **Bit-exact gate fundamentally requires the standalone C ref to
|
||
match the actual NEON call convention exactly.** When the layout
|
||
convention differs (as here), no amount of correct algorithm
|
||
transcription saves you. The cleanest fix is to either run
|
||
dav1d's own C ref (vendor more headers) or use dav1d's NEON
|
||
padding to construct tmp.
|
||
|
||
## What lands in this commit
|
||
|
||
- `external/dav1d-snapshot/src/arm/64/cdef_tmpl.S` (additional
|
||
vendored file, needed for cdef.S to include)
|
||
- `tests/cdef_ref.c` — standalone C ref (algorithmically correct,
|
||
layout known-mismatched)
|
||
- `tests/bench_neon_cdef.c` — bench harness with M1 made warning
|
||
(proceeds to M3 even on layout mismatch)
|
||
- `external/dav1d-snapshot/config.h` — asm preamble shim
|
||
(works — dav1d's cdef.S assembles + links + executes)
|
||
- `CMakeLists.txt` — dav1d asm + table source build wiring
|
||
- M3₅ baseline: 3.923 Mblock/s captured on hertz
|
||
|
||
## Resumption checklist (next session)
|
||
|
||
- [ ] Pick M1 resolution path (1, 2, or 3 from §"Resolution paths")
|
||
- [ ] If path 1: vendor + bind `dav1d_cdef_padding8_8bpc_neon`,
|
||
update bench to call padding-then-filter, recapture M1 gate
|
||
- [ ] Phase 4 plan QPU CDEF kernel (likely brief; predicted RED)
|
||
- [ ] Phase 5 review (mandatory; first AV1 QPU work)
|
||
- [ ] Phase 6 implement
|
||
- [ ] Phase 7 measure M2 + M4 if reaches threshold
|
||
- [ ] Confirm deployment recipe: CDEF stays on CPU (likely)
|