Skip to content

📦 WP-123 — feat(losses): RLE loss

Engineering log · Phase 12 — Keypoint detection, v0.5.0

🔗 Depends on WP-122 · Needed by WP-124, WP-132, WP-135

⬅️ WP-122 · Roadmap · WP-124 ➡️

WP-123 — a loss written by hand, from equations fetched three times

Not delegated. AGENTS.md sec. 6 reserves loss/assignment/optimizer/rotated-geometry modules for hand-written implementation from the equations -- the highest AGPL-regurgitation-risk surface -- and R14's RLE loss is squarely a loss module, the first one this project's Phase 12 work has touched. Every other WP in this phase (120/121/122) went to codex:codex-rescue; this one did not, and no part of it was delegated except (implicitly) nothing -- the module, its RealNVP flow, and its test suite are all written directly against R14's own equations, fetched from arXiv's HTML rendering (the allowlisted primary-source endpoint) across three separate targeted WebFetch passes rather than one broad read, specifically to cross-corroborate the one detail secondary summarization is most likely to get subtly wrong: which direction the flow runs and what sits at its latent end. All three fetches agreed on Eq. 8 verbatim, R14 sec. 4's K=6/Lfc=3/Nn=64/Leaky-ReLU conditioner config, and Eq. 12's affine coupling formula; the third fetch additionally recovered R14 sec. 3.2's z_bar ~ N(0, I) latent statement and sec. 3.3's sigma_hat sigmoid sentence -- resolving A65, which WP-122 had deliberately left open rather than guessed at. No detection-repository code, including R14's own official implementation, was opened at any point (AGENTS.md sec. 7).

The loss (Eq. 8, log s dropped per R14's own stated implementation choice): L_rle = -log Q(x_bar) - log G_phi(x_bar) + log sigma_hat, where x_bar = (mu_g - mu_hat) / sigma_hat is the standardized residual, Q a fixed unit Laplace (no parameters, R14's "preset density"), and G_phi the density the RealNVP flow induces over x_bar via the standard change-of-variables identity. Q's gradient reaching mu_hat/sigma_hat independently of the flow is R14's own stated "gradient shortcut" motivation for the additive split (sec. 3.2) rather than an incidental property -- test_gradient_shortcut_reaches_mu_and_sigma_with_flow_frozen freezes every flow parameter and confirms the regression head still receives nonzero gradient, a direct test of that specific paper claim rather than a generic backprop smoke test.

The flow (Eq. 12, Appendix A): six stacked affine coupling layers over the 2-D residual, each holding one coordinate fixed and transforming the other by a scale-and-shift pair from a 3-layer/64-unit/Leaky-ReLU conditioner. The paper states the coupling formula but not that the fixed/transformed partition alternates between layers -- that is RealNVP's own standard construction (Dinh et al., which R14 cites rather than restates), and without it one of the two coordinates could never move at all under the whole stack, making it degenerate rather than a valid bijection over R^2. Implemented as synthesize (the generative z -> x direction) and invert (x -> z, RealNVP's defining closed-form property, needed for density evaluation) as two directions of the same module rather than one direction plus a solved inverse.

What a unit gate can and cannot prove here: the mechanism-claim validation this row's roadmap text (and R14 itself) cares about -- RLE beating an OKS-only ablation -- needs the [GPU][PRINCIPAL]-gated WP-125 tier run this session cannot execute. Every property a unit test can pin was therefore treated as load-bearing rather than incidental: test_flow_density_integrates_to_one numerically integrates the flow's induced density over a 161x161 grid and checks it lands within 0.05 of 1.0 -- the single most discriminating check of change-of-variables bookkeeping, since a base-density term without its accompanying log-determinant (or vice versa) passes every other test in the file but does not integrate to a proper probability density. test_log_determinant_matches_autograd_jacobian compares the analytic per-layer log-determinant against torch.autograd.functional.jacobian's own determinant, independently of invert entirely -- self-consistency between synthesize and invert alone could not catch a sign error that happened to cancel between the two, and this check cannot make that mistake since it never calls invert.

Design decisions this row records rather than silently makes (A65, A66): sigma's positivity mapping (deferred by WP-122/A65) resolves here as torch.sigmoid, applied in the loss rather than the head, per R14 sec. 3.3's own stated sentence -- not chosen freehand. Visibility masking (A66, new): COCO v=0 is excluded unconditionally (no ground truth exists to train against), v=1/v=2 both contribute equally, since COCO's own OKS protocol already scores against v>=1 regardless of occlusion and training against a narrower set than the metric evaluates against would be the inconsistent choice, not the safe one.

A structural fix along the way: every other loss in this repo (instance_mask_loss, ciou_loss, probabilistic_iou, ...) is a parameter-free pure function over already-produced network outputs. RLE's flow is the first loss component in this project with trainable weights of its own, and a first draft reached for a lazily-built module-level global singleton to hold it -- convenient for a bare function's signature, but a violation of this project's own "no global mutable state" rule, undiscoverable by an optimizer's .parameters(), and unable to move devices or checkpoint the way a registered submodule does. Corrected before landing: RLELoss is an nn.Module owning the flow as self.flow, the shape every other stateful component in this codebase (DualDetectionHead, ProtoNet) already takes.