Skip to content

coef: two-head sigmoid (λ_soft, λ_hard) — .cvcnav format v2 - #105

Merged
transfix merged 2 commits into
mainfrom
feat/coef-mlp-two-head-sigmoid
Sep 27, 2026
Merged

transfix merged 2 commits into
mainfrom
feat/coef-mlp-two-head-sigmoid

Conversation

@transfix

@transfix transfix commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

What

Extend the deployable CoefMLP / coef_export / coef_train path with the paper's two learned, sigmoid-bounded reroute strengths — lam_soft and lam_hard (App A.4 / material_nav.py:175-176, lam = lam_max*sigmoid(head)). Previously only lam_soft was learnable (a single softplus 4th output); lam_hard was a fixed dial. This makes the tiny deployable net two-head, byte-compatible with the new libcvc .cvcnav format v2.

Companion (required host): transfix/libcvc — nav: two-head sigmoid (lam_soft, lam_hard) — .cvcnav format v2 → transfix/libcvc#436

Changes

  • sdf_nav.CoefMLP: use_lam_hard (requires use_lam) → out_dim = 3 + use_lam + use_lam_hard; lam_soft_max/lam_hard_max = 5.0/10.0. A two-head net is lam_sigmoid: lam columns are lam_max*sigmoid(head) with the head init in the last Linear bias (logit(init/max)); α/β/γ stay softplus(log(expm1)). full_out_bias() returns the 3 abg biases only in sigmoid mode; coeffs_and_lam returns (a,b,g,lam_soft[,lam_hard]). The v1 single-softplus lam path is unchanged.
  • add_lam_heads(model, soft_init, hard_init): basin-preserving lifter mirroring add_lam_head — appends the two lam rows (zero weights, bias = logit(init/max)), copies the abg rows verbatim so α/β/γ are bit-identical at init.
  • coef_export.write_coef_mlp: a sigmoid net writes FORMAT_VERSION = 2 + FLAG_LAM_SIGMOID, out_bias length 3 (abg only), then the two ceilings (struct '<ff') before the meta trailer. A plain / single-softplus-lam net stays v1, byte-unchanged.
  • coef_train: --learned-lam-hard (implies --learned-lam) builds the net via add_lam_heads; --lam-hard-init (>0) seeds the hard head. train_bicycle / eval_risk_exposure use the learned lam_hard for a two-head net; the Swarm drives with both learned lambdas. Deploy note states the 5-out v2 net + the v2 libcvc host floor.

Byte parity / back-compat

  • Added a pure-numpy .cvcnav decoder in tests/test_lam_head.py that reproduces the C++ forward from the exported bytes and asserts parity (~1e-4) + lam bounds — an independent cross-check that runs without a pycvc build.
  • tests/test_coef_mlp_parity.py gains a pycvc-gated in-process C++↔torch round-trip for the two-head net (all 5 columns, ~1e-4) — runs once pycvc is rebuilt on v2 libcvc.
  • v1 single-softplus export stays byte-identical (test_single_softplus_lam_still_exports_v1); all existing lam tests pass.

Test results (.venv, torch 2.12, numpy 2.4)

  • tests/test_lam_head.py: 14 passed (7 new two-head).
  • Regression sweep (test_swarm, test_scenario, test_squad, test_vehicle, test_material_scenario, test_drive_step_parity, test_sim_thread, test_material_parity, test_vehicle_refinements, test_coef_export_checkpoint, test_coef_train_lam_deploy, test_risk_lever, test_swarm_risk_drive, test_scorecard_eval, test_coef_eval, test_coef_mlp_parity): 130 passed, 9 skipped (all skips are pycvc-only).
  • black 24.10.0 + ruff 0.6.9: clean.

NOT in this PR (human owns)

Retrain / A-B / republish (step 9). Ready-to-retrain command:

grl-snam coef-train --rollout bicycle --w-risk 0.5 --learned-lam --learned-lam-hard \
  --lam-soft 0.4 --lam-hard-init 1.0 --steps 800 --seed <S> --out coef_mlp_riskaware_2head_800s<S>.cvcnav

The published libcvc-matext + cvc-dbg-weights recipes will need a cvc_revision bump at republish (not touched here).

Extend the deployable CoefMLP / coef_export / coef_train path with the paper's TWO
learned, sigmoid-bounded reroute strengths (lam_soft AND lam_hard, App A.4 /
material_nav.py:175-176: lam = lam_max*sigmoid(head)). Byte-compatible with the
libcvc cvc::nav::coef_mlp v2 loader.

sdf_nav.CoefMLP:
- Add use_lam_hard (requires use_lam) -> out_dim = 3 + use_lam + use_lam_hard, plus
  lam_soft_max/lam_hard_max (5.0/10.0). A two-head net is lam_sigmoid: the lam
  columns are lam_max*sigmoid(head) with the head init in the last Linear bias
  (logit(init/max)); alpha/beta/gamma stay softplus(log(expm1)). full_out_bias()
  returns the 3 abg biases only in sigmoid mode. coeffs_and_lam returns
  (a,b,g,lam_soft[,lam_hard]). The v1 single-softplus lam path is unchanged.
- add_lam_heads(model, soft_init, hard_init): basin-preserving lifter mirroring
  add_lam_head — append the lam_soft/lam_hard rows (zero weights, bias=logit(init/
  max)), copy the abg rows verbatim so alpha/beta/gamma are bit-identical at init.

coef_export.write_coef_mlp: a sigmoid net writes FORMAT_VERSION 2 + FLAG_LAM_SIGMOID,
out_bias length 3 (abg only), then the two ceilings (struct '<ff') before meta. A
plain / single-softplus-lam net stays v1, byte-unchanged.

coef_train: --learned-lam-hard (implies --learned-lam) builds the net via
add_lam_heads; --lam-hard-init (>0) seeds the hard head. train_bicycle and
eval_risk_exposure use the LEARNED lam_hard for a two-head net; the Swarm drives with
both learned lambdas. Deploy note states the 5-out v2 net + the v2 libcvc host floor.

tests: add_lam_heads identity + init + flags; two-head sigmoid bounds and out-of-range
init rejection; exporter v2 layout + a pure-numpy decode/forward parity (~1e-4) that
reproduces the C++ forward from the exported bytes; v1 single-softplus export
back-compat; a two-head train_bicycle smoke; the CLI rejecting --lam-hard-init <= 0;
and a pycvc-gated in-process C++ round-trip for the two-head net.
…built on v2 libcvc

The in-process C++<->torch round-trip loads a v2 .cvcnav through the INSTALLED pycvc,
which still links a pre-v2 libcvc and raises 'unsupported .cvcnav format version' in
hermetic CI. The comment already said it should skip until pycvc is rebuilt on v2
(deploy step 9); implement that skip. The pure-numpy decoder in test_lam_head already
proves the v2 byte layout + forward math, so byte-parity coverage is unaffected.
@transfix
transfix merged commit 0ff3696 into main Sep 27, 2026
3 checks passed
@transfix
transfix deleted the feat/coef-mlp-two-head-sigmoid branch September 27, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant