Skip to content

[C++ backend] Generalize degeneracy-triggered emit (no-CSE + coefficient threshold) beyond jointlock #543

Description

@siddhss5

Context

#536 hit a subtle cross-platform determinism failure in the committed C++ artifacts: the emitted header structure (not just float values) differed between macOS (Accelerate) and Linux CI (OpenBLAS), tripping cpp_emit --check. Root cause, two independent mechanisms, both specific to degenerate geometry:

  1. Numerical-noise coefficients. A jointlock arm's degenerate locked sub-chains (near-parallel axes at some lock samples) push poe_to_dh coefficient products down to ~1e-18..1e-49 — numerically zero, but their exact low bits (and whether they land at exactly 0) are BLAS-backend-sensitive. rizon4 carried 4829 such noise coefficients.

  2. CSE subexpression-sharing fragility (the deeper one). sympy.cse shares subexpressions by exact float equality. On a degenerate sub-chain, ill-conditioned poe_to_dh leaks BLAS noise into the low bits of genuine (not near-zero) coefficients, so two coefficients that are byte-equal on one platform differ on another → a share happens on one platform but not the other → different temp count → different header structure. No threshold or snap fully fixes this (any discretization straddles its own boundary).

The fix that landed in #536 (scoped to jointlock)

Applied only on the jointlock RR-unit path (_render_rr_unit(zero_threshold=1e-12)):

  • Snap near-canonical DH values (0, ±π/2, ±π) to exact at the source (_snap_dh), collapsing degenerate products before they become noise.
  • Threshold coefficients below 1e-12 * max(scale, 1.0) to zero before rendering (render_rr_coeffs(zero_threshold=...)).
  • No CSE (render_rr_coeffs(use_cse=False)): the structure is then exactly the (threshold-stabilised) non-zero-coefficient set, which IS backend-deterministic — no sharing to break. Bonus: after thresholding, the polynomials are short enough that no-CSE is smaller than the CSE'd form for these arms.

Local proof: per-sub-chain structure is invariant under a 1e-13 coefficient perturbation with use_cse=False but not with use_cse=True.

Why this is NOT generalized to all arms yet

  • The 60 general_6r arms are byte-stable across backends because their geometry is well-conditioned (deterministic poe_to_dh → byte-identical coefficients → byte-identical CSE), not by luck. They don't currently exhibit either mechanism.
  • general_6r is the perf-critical hot path (runtime-vended). CSE's sharing is genuinely valuable there; blanket no-CSE would likely bloat + slow it for a robustness it doesn't need (jointlock's no-CSE was only smaller because the threshold had already dropped most coefficients — a well-conditioned arm has little to drop).
  • Uniform application would also re-baseline all 60 committed headers for ~no benefit.

What to build

Make the robust path degeneracy-triggered, not solver-hardcoded:

  1. Degeneracy detector at emit time. For each arm/sub-chain, decide whether its RR derivation is degenerate/ill-conditioned. Candidate signals (pick the cheapest reliable one):
    • poe_to_dh produces a near-canonical twist (|alpha - k·π/2| < ~1e-6) or a near-zero link length — the parallel-axis fingerprint; and/or
    • the derived coefficient matrices contain a non-trivial mass of sub-threshold-noise coefficients (e.g. > N below 1e-12 * scale); and/or
    • a conditioning proxy on the RR pencil / m_quad.
  2. Route degenerate units to snap + threshold + no-CSE; well-conditioned units keep CSE. Replace the current zero_threshold > 0 (jointlock-only) trigger in _render_rr_unit with the detector, so a future degenerate general_6r arm (near-parallel-axis pair, etc.) gets the robust path automatically.
  3. Data first: measure no-CSE header size + runtime cost on a representative well-conditioned general_6r arm, to confirm the "keep CSE when well-conditioned" split is the right default (vs. e.g. no-CSE everywhere if the cost turns out negligible).
  4. Guard: a test that asserts emit determinism under a small (~1e-13) coefficient perturbation for any degenerate-flagged unit (the local proxy from jointlock.seven_r native (rizon4 + rizon10) + native-family bulletproofing (#491, #533, #534, #535) #536, promoted to a CI test), so this class of drift can't silently regress.

Acceptance

  • Any degenerate arm/sub-chain (not just jointlock) emits a backend-deterministic header; well-conditioned arms keep CSE and their committed bytes unchanged.
  • The degeneracy trigger is explicit and tested; no arm relies on "byte-stable by luck".
  • Decision on the well-conditioned default is backed by measured no-CSE size/perf numbers.

References

Priority: after the native-coverage push (HP kernel #491, then all-72). Not urgent — it's future-proofing + consistency, and the current jointlock scoping is correct and green.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions