mirror of
https://github.com/ruvnet/RuView
synced 2026-08-11 20:41:44 +00:00
refactor(beyond-sota): ADR-155 M2 — host-verifiable §8 closeout (7 de-magic, 9 boundary tests, native-conv honest-null) (#1059)
* refactor(train): ADR-155 M2 §8 — de-magic train non-tch tuning constants + boundary tests Lift bare numeric literals used as thresholds / guard epsilons in the non-tch (host-verifiable) train surface into named, documented consts and pin each set with a *_consts_unchanged_from_literals test. Values are bit-identical to the prior inline literals — cleanup, no behaviour change. De-magicked (const + pin test): - metrics_core.rs: VISIBILITY_THRESHOLD (0.5), MIN_REFERENCE_EXTENT (1e-6), OKS_FALLBACK_SIGMA (0.07) - ruview_metrics.rs: NUM_KEYPOINTS (17), VISIBILITY_THRESHOLD (0.5), PCK_THRESHOLD (0.2), MIN_BBOX_DIAG (1e-3), MIN_DURATION_MINUTES (1e-6) - subcarrier.rs: SPARSE_BASIS_SIGMA (0.15), SPARSE_BASIS_THRESHOLD (1e-4), SPARSE_REGULARIZATION_LAMBDA (0.1), SPARSE_COO_PRUNE_EPS (1e-8), SPARSE_SOLVER_TOL (1e-5 f64), SPARSE_SOLVER_MAX_ITERS (500) - eval.rs: MIN_POSITIVE_MPJPE (1e-10) - domain.rs: LAYER_NORM_EPS (1e-5) - virtual_aug.rs: BOX_MULLER_U1_FLOOR (1e-10), MIN_ROOM_SCALE (1e-10) Boundary / characterization tests (pin CURRENT behaviour): - visibility_threshold_boundary_is_inclusive (>= 0.5 at the edge) - degenerate_extent_below_floor_is_unscoreable ((0,0,0.0)/0.0, not perfect) - tracking_zero_duration_does_not_divide_by_zero - oks_short_array_is_bounded_at_keypoint_count (16 rows, no panic) - compute_interp_weights_single_target_is_index_zero (target_sc==1) - sparse_interp_single_target_is_finite - domain_gap_infinite_when_in_domain_perfect_but_cross_nonzero - domain_gap_unity_when_everything_perfect - augment_frame_zero_room_scale_passes_amplitude_finite Doc-only (no behaviour change): - rapid_adapt.rs: correct module-doc O(eps) -> O(eps^2) for central differences - geometry.rs: add # Panics to DeepSets::encode (documents existing assert!) train --no-default-features: 191 lib (was 176), 303 total (was 288), 0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(nn): ADR-155 M2 §3 — pure-Rust LinearHead::try_new input guard + de-magic softplus threshold ADR-155 §3 found rf_encoder.rs has no adversarial checkpoint-deserialization assert — its assert_eq!s in LinearHead::new are construction-time API contracts on programmer-supplied vectors. This adds the honest, in-scope improvement the M2 task allows: a pure-Rust *fallible* constructor so weights from an untrusted / deserialized checkpoint can be shape-validated without panicking. - Add RfHeadError (WeightShape / BiasShape / VarWeightShape) + Display + Error. - Add LinearHead::try_new returning Result<Self, RfHeadError>; on success the head is byte-identical to LinearHead::new. new() is unchanged (still asserts; now documents # Panics and points to try_new) — no behaviour change for existing callers. - De-magic softplus's bare 20.0 overflow threshold into SOFTPLUS_LINEAR_THRESHOLD (value unchanged) + pin test. Tests: try_new_accepts_valid_and_rejects_each_bad_shape (valid == new forward; each bad shape → typed error), softplus_threshold_unchanged_from_literal. nn --no-default-features lib: 37 passed (was 35), 0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> * perf(nn): ADR-155 M2 §4 — native-conv bench-first → MEASURED-INCONCLUSIVE (no perf change shipped) The §8 "native-conv naive-loop rewrite" backlog item: DensePoseHead:: apply_conv_layer is a pure-Rust 6-nested-loop conv (benchable on this host, not tch/ort-gated). Bench-first per the §0 PROOF discipline. - Add committed criterion bench benches/native_conv_bench.rs measuring forward() through the naive conv on representative single-layer configs (--no-default- features; no ort download). - Prototyped a bit-identical range-clamped variant (hoist the per-tap in-bounds branch by pre-clamping kh/kw ranges; same ic→kh→kw MAC order ⇒ bit-identical). MEASURED before/after on this host: ~35% faster on padding-heavy small-channel maps (4.40→2.84 ms) but a ~3% *regression* on channel-heavy maps (11.09→11.48 ms), all inside a ±20% run-to-run noise floor. Verdict: INCONCLUSIVE — the benefit is not robustly positive, so the rewrite is NOT shipped and NOT a fabricated speedup. Reverted to the naive loop; honestly deferred (ADR-155 §8). - Add native_conv_matches_reference: a hand-computed characterization anchor (1×1 = scalar MAC; same-padded 3×3 ones = truncated-window sums 9/6/4) pinning CURRENT conv behaviour for any future rewrite. nn --no-default-features lib: 38 passed (was 37), 0 failed. No behaviour change. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-155): M2 §8.2 — enumerated host-verifiable P3 backlog clearance + CHANGELOG Replace the §8 bulk "~40 lower-severity findings" line with the real, enumerated M2 resolution (§8.2): 7 de-magicked (const + pin == prior literal), 9 boundary tests, 1 input guard (rf_encoder try_new), 2 doc-only, 1 perf bench-first MEASURED-INCONCLUSIVE (not shipped). Mark native-conv + rf_encoder RESOLVED; state which §8 items stay data-gated (GraphPose-Fi/INT4/CSI-JEPA) or tch-gated (proof/trainer/model panic sites, metrics *_v2 dead code) and ONNX read-lock upstream-gated — blocked, not dropped. Declare the non-tch-verifiable subset of §8 cleared. Validation: train --no-default-features 303 passed (was 288); nn lib 38 (was 35); workspace --no-default-features 3,293 passed, 0 failed; Python proof VERDICT PASS, hash f8e76f21…46f7a UNCHANGED bit-exact. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -98,8 +98,64 @@ pub struct LinearHead {
|
||||
var_b: f32,
|
||||
}
|
||||
|
||||
/// A shape mismatch when building a [`LinearHead`] from supplied weights.
|
||||
///
|
||||
/// Returned by [`LinearHead::try_new`] so a caller loading weights from an
|
||||
/// **untrusted / deserialized** source can validate the tensor shapes without
|
||||
/// the panic that [`LinearHead::new`] raises on a programmer-supplied mismatch
|
||||
/// (ADR-155 M2 §3: a pure-Rust input guard ahead of the construction contract).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RfHeadError {
|
||||
/// `w.len()` was not `out_dim * EMBEDDING_DIM`.
|
||||
WeightShape {
|
||||
/// Expected length (`out_dim * EMBEDDING_DIM`).
|
||||
expected: usize,
|
||||
/// Actual `w.len()`.
|
||||
got: usize,
|
||||
},
|
||||
/// `b.len()` was not `out_dim`.
|
||||
BiasShape {
|
||||
/// Expected length (`out_dim`).
|
||||
expected: usize,
|
||||
/// Actual `b.len()`.
|
||||
got: usize,
|
||||
},
|
||||
/// `var_w.len()` was not `EMBEDDING_DIM`.
|
||||
VarWeightShape {
|
||||
/// Expected length (`EMBEDDING_DIM`).
|
||||
expected: usize,
|
||||
/// Actual `var_w.len()`.
|
||||
got: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RfHeadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::WeightShape { expected, got } => {
|
||||
write!(f, "weight shape mismatch: expected {expected}, got {got}")
|
||||
}
|
||||
Self::BiasShape { expected, got } => {
|
||||
write!(f, "bias shape mismatch: expected {expected}, got {got}")
|
||||
}
|
||||
Self::VarWeightShape { expected, got } => {
|
||||
write!(f, "var weight shape mismatch: expected {expected}, got {got}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RfHeadError {}
|
||||
|
||||
impl LinearHead {
|
||||
/// Build a head with given weights. `w.len()` must be `out_dim * EMBEDDING_DIM`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics on a shape mismatch (`w`/`b`/`var_w`). This is a construction-time
|
||||
/// API contract on *programmer-supplied* vectors. For weights from an
|
||||
/// untrusted / deserialized source, prefer [`LinearHead::try_new`], which
|
||||
/// returns a typed [`RfHeadError`] instead of panicking.
|
||||
#[must_use]
|
||||
pub fn new(task: TaskKind, out_dim: usize, w: Vec<f32>, b: Vec<f32>, var_w: Vec<f32>, var_b: f32) -> Self {
|
||||
assert_eq!(w.len(), out_dim * EMBEDDING_DIM, "weight shape mismatch");
|
||||
@@ -108,6 +164,40 @@ impl LinearHead {
|
||||
Self { task, w, b, out_dim, var_w, var_b }
|
||||
}
|
||||
|
||||
/// Fallible constructor: validate the weight shapes and return a typed
|
||||
/// [`RfHeadError`] on mismatch instead of panicking (ADR-155 M2 §3).
|
||||
///
|
||||
/// Use this when `w` / `b` / `var_w` originate from a checkpoint or any
|
||||
/// untrusted source. On success the produced head is byte-for-byte identical
|
||||
/// to [`LinearHead::new`] with the same arguments.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`RfHeadError`] when any of:
|
||||
/// - `w.len() != out_dim * EMBEDDING_DIM`
|
||||
/// - `b.len() != out_dim`
|
||||
/// - `var_w.len() != EMBEDDING_DIM`
|
||||
pub fn try_new(
|
||||
task: TaskKind,
|
||||
out_dim: usize,
|
||||
w: Vec<f32>,
|
||||
b: Vec<f32>,
|
||||
var_w: Vec<f32>,
|
||||
var_b: f32,
|
||||
) -> Result<Self, RfHeadError> {
|
||||
let expected_w = out_dim * EMBEDDING_DIM;
|
||||
if w.len() != expected_w {
|
||||
return Err(RfHeadError::WeightShape { expected: expected_w, got: w.len() });
|
||||
}
|
||||
if b.len() != out_dim {
|
||||
return Err(RfHeadError::BiasShape { expected: out_dim, got: b.len() });
|
||||
}
|
||||
if var_w.len() != EMBEDDING_DIM {
|
||||
return Err(RfHeadError::VarWeightShape { expected: EMBEDDING_DIM, got: var_w.len() });
|
||||
}
|
||||
Ok(Self { task, w, b, out_dim, var_w, var_b })
|
||||
}
|
||||
|
||||
/// A zero-initialised head (uncertainty = softplus(0) ≈ 0.693).
|
||||
#[must_use]
|
||||
pub fn zeros(task: TaskKind, out_dim: usize) -> Self {
|
||||
@@ -136,9 +226,14 @@ impl LinearHead {
|
||||
}
|
||||
}
|
||||
|
||||
/// Input magnitude above which `softplus(x) ≈ x` to f32 precision, so the
|
||||
/// `exp` is skipped to avoid overflow (ADR-155 M2 §8: de-magicked from a bare
|
||||
/// `20.0`; value unchanged). At x = 20, `ln(1+e^20) − 20 ≈ 2e-9`, below f32 eps.
|
||||
const SOFTPLUS_LINEAR_THRESHOLD: f32 = 20.0;
|
||||
|
||||
fn softplus(x: f32) -> f32 {
|
||||
// Numerically stable softplus.
|
||||
if x > 20.0 {
|
||||
if x > SOFTPLUS_LINEAR_THRESHOLD {
|
||||
x
|
||||
} else {
|
||||
(1.0 + x.exp()).ln()
|
||||
@@ -270,6 +365,48 @@ mod tests {
|
||||
RfEmbedding::new(vec![fill; EMBEDDING_DIM])
|
||||
}
|
||||
|
||||
/// ADR-155 M2 §8: the de-magicked softplus linear-threshold must equal the
|
||||
/// prior inline `20.0` literal exactly (operating-value guard).
|
||||
#[test]
|
||||
fn softplus_threshold_unchanged_from_literal() {
|
||||
assert_eq!(SOFTPLUS_LINEAR_THRESHOLD, 20.0_f32);
|
||||
}
|
||||
|
||||
/// ADR-155 M2 §3: `try_new` accepts correctly-shaped weights and produces a
|
||||
/// head byte-identical to `new`, but returns a typed error on a mismatched
|
||||
/// (e.g. corrupt-checkpoint) shape instead of panicking.
|
||||
#[test]
|
||||
fn try_new_accepts_valid_and_rejects_each_bad_shape() {
|
||||
let out_dim = 2;
|
||||
let w = vec![0.0; out_dim * EMBEDDING_DIM];
|
||||
let b = vec![0.0; out_dim];
|
||||
let var_w = vec![0.0; EMBEDDING_DIM];
|
||||
|
||||
// Valid: try_new == new (forward identical on a probe embedding).
|
||||
let head = LinearHead::try_new(TaskKind::Presence, out_dim, w.clone(), b.clone(), var_w.clone(), 0.0)
|
||||
.expect("valid shapes must construct");
|
||||
let reference = LinearHead::new(TaskKind::Presence, out_dim, w.clone(), b.clone(), var_w.clone(), 0.0);
|
||||
assert_eq!(head.forward(&emb(0.5)).values, reference.forward(&emb(0.5)).values);
|
||||
|
||||
// Bad weight length.
|
||||
assert_eq!(
|
||||
LinearHead::try_new(TaskKind::Presence, out_dim, vec![0.0; 3], b.clone(), var_w.clone(), 0.0)
|
||||
.unwrap_err(),
|
||||
RfHeadError::WeightShape { expected: out_dim * EMBEDDING_DIM, got: 3 }
|
||||
);
|
||||
// Bad bias length.
|
||||
assert_eq!(
|
||||
LinearHead::try_new(TaskKind::Presence, out_dim, w.clone(), vec![0.0; 1], var_w.clone(), 0.0)
|
||||
.unwrap_err(),
|
||||
RfHeadError::BiasShape { expected: out_dim, got: 1 }
|
||||
);
|
||||
// Bad var-weight length.
|
||||
assert_eq!(
|
||||
LinearHead::try_new(TaskKind::Presence, out_dim, w, b, vec![0.0; 5], 0.0).unwrap_err(),
|
||||
RfHeadError::VarWeightShape { expected: EMBEDDING_DIM, got: 5 }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn head_forward_produces_values_and_finite_uncertainty() {
|
||||
let head = LinearHead::zeros(TaskKind::Presence, 2);
|
||||
|
||||
Reference in New Issue
Block a user