Handoff: rate control is next, and it is unsound as written
Session 5 handoff. The user has chosen rate control as the next session's work, so this reads ratectl.py properly before that session starts rather than discovering the problem mid-implementation. FINDINGS 26: encode_rate_controlled() is not sound. H.encode() is temporally recursive -- SKIP blocks copy the previous RECONSTRUCTION -- but rate control builds a ladder of independent whole-sequence encodes and picks each frame from whichever rung fits the budget. Frames then reference reconstructions the decoder never saw. Measured on the Singe window: 67 rung switches, 111 of 120 frames drift, worst frame 43.4% of pixels, reported PSNR overstated by 0.36 dB. It would have wired up cleanly and reported a plausible wrong answer. Two further defects in the same function: the lam ladder runs to 2e5, 250x past the FINDINGS 15 cliff, so a frame that only fits up there is destroyed rather than rate-controlled; and with 5 rungs only two are ever chosen, straddling the operating point by 7.5x. The docstring describes a per-frame binary search, which is the right design -- the implementation is a fixed ladder. The leaky bucket does work and should be kept: 109.1 KB/s against a 110 target. tools/analysis/09_ratectl_drift.py is the regression test and the acceptance criterion: it exits non-zero until zero frames drift. Also corrected the stale 38% blit figure in ratectl.py's profile commentary, which session 5 measured at 53.6% (FINDINGS 24), and recorded the pgrep -f self-kill trap again -- four times across three sessions now. check.sh ALL GREEN. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -1003,3 +1003,73 @@ One 10 s window of one stream, at fixed lam, with `_paint` still a Python loop.
|
||||
The full-disc survey is still not done, and the numbers above are the *worst*
|
||||
window rather than a distribution over content. What has changed is that the
|
||||
worst case is now a measurement rather than a worry.
|
||||
|
||||
---
|
||||
|
||||
## 26. Rate control is unsound as written — found before wiring it up (session 5)
|
||||
|
||||
FINDINGS 25.3 promoted rate control from insurance to a requirement. Reading
|
||||
`ratectl.py` before wiring it into `encode.py` turned up a correctness bug that
|
||||
would have produced exactly the kind of plausible-looking wrong result this
|
||||
project keeps catching (FINDINGS 4, 9, 14, 18).
|
||||
|
||||
### 26.1 The lam ladder desynchronises the encoder from the decoder
|
||||
`H.encode()` is **temporally recursive**: SKIP blocks are copied from the
|
||||
previous *reconstruction*, and `prev = out` closes the loop
|
||||
(`vq_hybrid.py:84-109`). A frame's output therefore depends on every frame
|
||||
before it in that same run.
|
||||
|
||||
`encode_rate_controlled()` runs `H.encode()` once per lam over the **whole
|
||||
sequence**, building a ladder of independent temporal chains, then picks each
|
||||
frame from whichever rung fits the budget. When frame *f* comes from rung *i*
|
||||
and frame *f-1* was emitted from rung *j != i*, the SKIP blocks in *f* reference
|
||||
a reconstruction **the decoder never saw**.
|
||||
|
||||
Measured on the Singe window (`tools/analysis/09_ratectl_drift.py`, 120 frames,
|
||||
5 rungs, target 110 KB/s):
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| rung switches | **67** over 120 frames |
|
||||
| frames whose emitted output differs from what the encoder recorded | **111 / 120** |
|
||||
| worst frame | **21,339 px = 43.4% of the frame** |
|
||||
| encoder-vs-decoder agreement, worst frame | 27.1 dB |
|
||||
| reported PSNR overstatement | **0.36 dB** |
|
||||
|
||||
The 0.36 dB is the least interesting number here. The encoder is reporting
|
||||
quality for a reconstruction that will never exist, and 43% of a frame differing
|
||||
is a visible artefact whatever the mean says.
|
||||
|
||||
**The fix is structural, not a tuning change:** `H.encode()` must become
|
||||
frame-drivable — take `prev` and one lam, return one frame — so rate control can
|
||||
feed back the frame it actually emitted. The current whole-sequence signature is
|
||||
what makes the ladder tempting in the first place.
|
||||
|
||||
### 26.2 The ladder spans 250x past the shippable range
|
||||
`lam_hi=2e5`, but FINDINGS 15 puts the quality cliff between lam=800 and
|
||||
lam=2000 and says do not ship past lam~800. Every rung above ~800 is
|
||||
unshippable, so a frame that only fits at lam=9457 has not been rate-controlled,
|
||||
it has been destroyed. Cap `lam_hi` at 800 and let a frame that cannot fit
|
||||
overrun the bucket — a visible overrun is a better failure than silent garbage.
|
||||
|
||||
### 26.3 The ladder is far too coarse where it matters
|
||||
With `steps=5` the geomspace lands on 1 / 21 / 447 / 9457 / 200000, and **only
|
||||
two rungs were ever chosen**. The budget is 8,721 B/frame; the two straddling
|
||||
rungs deliver 23,183 B (lam=21) and 3,071 B (lam=447) — a **7.5x** gap across
|
||||
the operating point. Rate control cannot land near a target it has to jump over.
|
||||
|
||||
The module docstring already describes the right approach — *"per frame we
|
||||
binary-search lam to land inside a byte budget"* — but the implementation is a
|
||||
fixed precomputed ladder. Doc and code disagree; the doc is correct.
|
||||
|
||||
### 26.4 What does work
|
||||
The leaky bucket lands the mean where it should: **109.1 KB/s against a 110
|
||||
target**, with 32% of frames over the per-frame budget and banked by the bucket.
|
||||
That mechanism is sound and worth keeping. It is the per-frame lam *selection*
|
||||
underneath it that needs rebuilding, not the bucket.
|
||||
|
||||
### 26.5 Cost note before starting
|
||||
Each rung is a full-sequence encode and `_paint` is still a Python per-block
|
||||
loop, so a 5-rung run over 120 frames takes minutes. **Vectorise `_paint`
|
||||
first** — it is already on the list for the full-disc survey and it makes the
|
||||
rate-control work practical rather than merely faster.
|
||||
|
||||
Reference in New Issue
Block a user