You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#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:
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.
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).
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:
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.
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.
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).
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.
Memory: emit determinism across BLAS backends; the drift boundary ADR-0001.
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.
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:Numerical-noise coefficients. A jointlock arm's degenerate locked sub-chains (near-parallel axes at some lock samples) push
poe_to_dhcoefficient 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.CSE subexpression-sharing fragility (the deeper one).
sympy.cseshares subexpressions by exact float equality. On a degenerate sub-chain, ill-conditionedpoe_to_dhleaks 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_dh), collapsing degenerate products before they become noise.1e-12 * max(scale, 1.0)to zero before rendering (render_rr_coeffs(zero_threshold=...)).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=Falsebut not withuse_cse=True.Why this is NOT generalized to all arms yet
poe_to_dh→ byte-identical coefficients → byte-identical CSE), not by luck. They don't currently exhibit either mechanism.What to build
Make the robust path degeneracy-triggered, not solver-hardcoded:
poe_to_dhproduces a near-canonical twist (|alpha - k·π/2| < ~1e-6) or a near-zero link length — the parallel-axis fingerprint; and/or1e-12 * scale); and/orm_quad.zero_threshold > 0(jointlock-only) trigger in_render_rr_unitwith the detector, so a future degenerate general_6r arm (near-parallel-axis pair, etc.) gets the robust path automatically.Acceptance
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.