Skip to content

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

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

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

Conversation

@transfix

Copy link
Copy Markdown
Owner

What

Extend the deployable hot-loop policy cvc::nav::coef_mlp with the paper's two learned, sigmoid-bounded reroute strengths — lam_soft and lam_hard (matching coef_energy_net / material_nav, lam = lam_max*sigmoid(head)). Previously the deployed net learned only lam_soft (a single softplus 4th output) and lam_hard was a fixed material_config dial. This introduces .cvcnav format v2, byte-compatible with the Python exporter.

Companion (produces the v2 blob): CVC-Lab/GRL-SNAM#105 — coef: two-head sigmoid (λ_soft, λ_hard) — .cvcnav format v2.

Format contract (v2)

  • kFormatVersion bumped 1 → 2; the loader accepts {1, 2} — a v1 file still loads and forwards identically, and a v2 file hard-fails on a pre-v2 host (safe: no silent misread). A plain/all-softplus net still writes v1 so it loads on old hosts; only a sigmoid-lam net bumps the version.
  • New flag kFlagLamSigmoid = 1u << 3: lam output columns (index ≥ 3) are lam_max*sigmoid(raw); α/β/γ (cols 0..2) stay softplus(net + log(expm1(bias))). The lam columns carry no out_bias offset — their bias lives in the last Linear layer, exactly as torch.
  • lam_soft_max_/lam_hard_max_ (5.0 / 10.0, per coef_energy_net.h) members + getters; has_lam_hard() (out ≥ 5, col 4). A v2 file writes the two ceilings after the out_bias block (out_bias length 3, abg only), before the meta trailer.

Changes

  • coef_mlp.h/.cpp: version + flag + maxes; per-column activation in forward(); from_layers() carries the sigmoid flag + maxes; save() round-trips the v2 trailer; default_biased()/legacy nets stay v1.
  • drive.cpp: drive_step_material and drive_step_material_ext extract the learned lam_hard (5th output) into md.lam_hard when has_lam_hard(), mirroring the existing lam_soft extraction. lam_hard is never gated (material.h), so it needs no witness gate. drive_telemetry gains a lam_hard field, captured at the force site.
    • Note (pre-existing, unchanged here): the learned-lam_soft path overrides md.lam_soft and thereby bypasses the witness gate (drive.cpp ~757). lam_hard mirrors this and is never gated; the lam_soft gate-bypass is left as-is (out of scope).

Tests (nav_material_deploy_test.cpp, +6)

  • Round-trip: a 5-output v2 net serializes and reloads with out=5 / has_lam_hard() / maxes intact; forward byte-identical across the round-trip.
  • Forward parity + bounds: outputs match a numpy-equivalent reference (~1e-4); lam columns bounded in [0, max]; the sigmoid ceiling clamps under extreme input (saturates to {0, max}).
  • On-disk byte layout: the exact v2 layout the Python exporter must match (version 2, flags, out_bias_len == 3, two ceilings, exact EOF).
  • Back-compat: a v1 net (out ≤ 4, no sigmoid flag) still loads and forwards all-softplus, lam col read as the v1 softplus fold.
  • Consumption: a learned-lam_hard net reroutes harder away from a near hard hazard than a small-lam_hard net.

Test results (lean nav build, CUDA/pycvc off)

  • nav_material_deploy_test: 12 passed (6 new v2).
  • nav_material_test: 28 passed; nav_test: 46 passed; nav_coef_train_test: 8 passed — v1 back-compat confirmed across the whole nav suite.

NOT in this PR (human owns)

Retrain / A-B / republish (step 9). The published libcvc-matext + cvc-dbg-weights recipes will need a cvc_revision bump at republish — not touched here (leaves a clean "ready to retrain" state).

Extend the deployable cvc::nav::coef_mlp policy with the paper's TWO learned,
sigmoid-bounded reroute strengths (lam_soft AND lam_hard), matching the
coef_energy_net / material_nav two-head form (lam = lam_max*sigmoid(head)).

coef_mlp.h/.cpp:
- Bump kFormatVersion 1->2; the loader ACCEPTS {1,2} (v1 files still load and
  forward identically; a v2 file hard-fails on a pre-v2 host, which is safe).
- Add kFlagLamSigmoid (1<<3): lam output columns (index >= 3) are
  lam_max*sigmoid(raw); alpha/beta/gamma stay softplus(log(expm1)). lam columns
  carry no out_bias offset (their bias lives in the last Linear layer).
- Add lam_soft_max_/lam_hard_max_ (5.0/10.0, per coef_energy_net) + getters,
  has_lam_hard() (out>=5, col 4), lam_sigmoid(). v2 writes the two ceilings after
  the out_bias block (abg-only, length 3); a plain net stays v1, byte-unchanged.
- from_layers carries the sigmoid flag + maxes; forward() applies the per-column
  activation; save() round-trips the v2 trailer.

drive.cpp: drive_step_material and drive_step_material_ext extract the learned
lam_hard (5th output) into md.lam_hard when has_lam_hard(), mirroring the existing
lam_soft extraction. lam_hard is never gated (material.h). drive_telemetry gains a
lam_hard field, captured at the force site.

tests (nav_material_deploy_test): v2 round-trip keeps out=5/has_lam_hard/maxes;
forward parity vs a numpy-equivalent reference (~1e-4) + lam bounds in [0,max];
the sigmoid ceiling clamps under extreme input; the on-disk v2 byte layout the
Python exporter must match; v1 back-compat (out<=4, no sigmoid flag, all-softplus);
and a learned-lam_hard hard-hazard reroute case.

Byte-compatible with GRL-SNAM's coef_export/coef_train two-head v2 path.
@transfix
transfix merged commit dc02fa5 into master Sep 27, 2026
13 checks passed
@transfix
transfix deleted the feat/coef-mlp-two-head-sigmoid branch September 27, 2026 22:45
transfix added a commit that referenced this pull request Sep 27, 2026
…uard gRPC deps off wasm (rev 2->3) (#438)

Coordinated wasm-mt republish so the catalog carries the coef_mlp .cvcnav format v2
(two-head sigmoid, #436), and to fix two wasm defects:
 - CVC_STATE_EXEC was wrongly forced OFF in the wasm build; the state_exec evaluator
   has no gRPC dependency and must always build. Turned ON.
 - the gRPC/xmlrpc transport deps (openssl/c-ares/re2/abseil/protobuf/grpc) were declared
   for ALL platforms incl wasm, but wasm builds without that transport and they are not
   published for wasm -> 'cvcpkg install cvc/libcvc --platform wasm-mt' could not resolve.
   Guarded to the native build matrix (linux/macos/windows).
cvc_revision 2->3 so publish-cvcgl-wasm produces a fresh 3.4.0+cvc.3.
transfix added a commit that referenced this pull request Sep 28, 2026
…bcvc (#449)

The published pycvc-cp311/312/313 +cvc.2 link a PRE-v2 libcvc, so the torch<->C++
coef_mlp parity test (GRL-SNAM test_coef_mlp_parity::test_two_head_sigmoid_matches_torch)
skips on 'unsupported .cvcnav format version'. Bump so publish-cvcpkg rebuilds pycvc against
the v2 libcvc (.cvcnav format v2 / two-head sigmoid, #436) and the parity test runs.
Recipe-only revision bump; disjoint from the wasm-guard change (#448).
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