VII · Apps / CHAPTER 68
Cameral sandwiches + AAI Lucy
github.com/openfluke/welvet/layers/parallel✅ cameral
Why it exists
A single Dense chain cannot host two independent weight copies that share an input, merge, and optionally train under different updates. That is the cameral graph: hemispheres, not screen-space sprites and not a second hidden size.
What it is
Hemispheres / Bicameral / Sandwich / Mix. Stem → Parallel mid → Dense head. SetBranchModes + TrainStackMSE. Lucy races in AAI (test41 / test48 / test50) import this API; measuring is lucy/; harness is not engine.
x → Dense stem → ┬→ Hemi 1 (own W, own TrainMode)
├→ Hemi 2
└→ Hemi n merge add|avg|concat|filter
→ Dense head → ŷ
MSE → g_y → each branch’s updateWhy cameral exists
Welvet already has Sequential (ordered compose) and Residual (skip). Neither is “two brains on one x.”
A hemisphere is an independent sub-net with its own weights. Bi / Tri / Quad are n hemispheres plus a merge.
Mix stamps a different TrainMode per hemi (SetBranchModes) so left can be StepBP
while right is TweenChain — one loss on the merge. Grid training.Step applies one mode to the whole net;
Mix needs TrainStackMSE or the BranchModes stamp is a lie.
The sandwich (stem → mid → head) is not extra depth for its own sake. Credit modes need a head Jacobian (or a W_head^T proxy). Without a head, FastProxy / HeadProxy have nothing to inject. Stem gets the down-going vector after hemispheres merge. That is why AAI Lucy jobs are sandwiches even when cameral count is 1 (mid is a single Dense, not Parallel).
API (engine, public)
Hemispheres/HemispheresFrom— n twins; merge add/avg/concat/filterBicameral/BicameralFrom/Sandwich— stem → Parallel → head StackSetBranchModes/TrainStackMSE— Mix pathResidualGraft— y = F(x)+x when F is ParallelWriteCameralFile/LoadCameral— cameral.entity(inmodel/entity)CamSync— optional weight averaging across cams / layers → §70
Layer package chapter: §27. Named updates: §67. Weight sync across cams (and across mesh layouts when shapes match): §70.
AAI Lucy benches
AAI is a separate tree. It replaces github.com/openfluke/welvet (public chaosglue module).
The engine does not import AAI. Lucy math is §66.
| Bench | What it answers |
|---|---|
| test41 | Native cameral Lucy (Bi/Tri/Mix) on toys — does the sandwich train at all. |
| test48 | Credit sweep: layers × dtypes × short jobs. Home of the equations in §67. |
| test50 | Deep FP32 race: all 29 named modes × Dense/Bi/Tri × 1³/2³/3³ origin-only. Rival = hard Acc vs StepBP. |
test50 copy: Split / Alt / MeshSplit family about +5 to +12 Acc over StepBP (~68–74%). Sine: Acc ceiling (many modes 100% including StepBP); FastProxy SoftAcc ~56–68 vs StepBP ~42–53. XOR: almost everyone at 75% (3 of 4 bits) — parking lot. Sparse wins Score, not Acc. Origin-only cubes: extra cells are disabled; 3³ is hop topology, not 27 copies.
w2a Test49 is the permutation smoke of those 29 modes (in [0] Run ALL), not a Lucy race.
Do not quote Test49 as “we beat backprop.”
Go example
cd welvet/examples/68-cameral && source ../env.sh && go run .package main
import (
"fmt"
"github.com/openfluke/welvet/core"
"github.com/openfluke/welvet/layers/parallel"
"github.com/openfluke/welvet/quant"
)
func main() {
s, err := parallel.Bicameral(8, 16, 1, core.ActivationLeakyReLU,
core.DTypeFloat32, quant.FormatNone)
if err != nil {
panic(err)
}
hemi := s.Children[1].(*parallel.Layer)
hemi.SetBranchModes(parallel.ModeStepBP, parallel.ModeTweenSplitFastProxy)
x := core.NewTensor[float32](1, 8)
t := core.NewTensor[float32](1, 1)
t.Data[0] = 0.5
loss, err := parallel.TrainStackMSE(s, x, t, parallel.ModeStepBP, 0.01)
fmt.Println("mix loss", loss, "err", err)
}
Output
mix loss 0 err <nil>