Book contents +

VII · Apps / CHAPTER 70

CamSync — inter-cameral / cross-mesh weight blend

Samuel Watson · Welvet feature bookOriginal source ↗

github.com/openfluke/welvet/layers/parallel✅ CamSync


Why it exists

Mix BranchModes and different inits let cams diverge on purpose. Sometimes you want them to share — gently (1% pull) or hard (full average) — within a Parallel, across Stack children, or even between same-shaped stores sitting on wildly different mesh layouts (1×2×1 ↔ 2×4×5). That is CamSync: couple weights without collapsing the graph into one Dense.

What it is

CamSyncConfig{Alpha, When, Groups, Cross}. BlendStores pulls every store in a clique toward the mean. Empty Groups = all cams; Cross wires SyncEndpoint pairs across layers/cams. Shape rule: Rows×Cols must match. Bidirectional today; one-way teacher→student not yet.

Parallel mid (n cams)                 Stack / mesh (any DxHxW)
  ┌─ cam0 W ─┐                          cell A 1×2×1     cell B 2×4×5
  │  cam1 W  │  Groups / all-cams       layer-1 Dense    cam3 layer-1 Dense
  │  cam2 W  │  α·mean blend     OR     [128×64]  ←──Cross──→  [128×64]
  └─ cam3 W ─┘                          (mesh size irrelevant — shape is)
         │
    SyncAfterSample | Step | Pulse | Manual SyncNow
Same-shaped stores sync. Mesh topology is host wiring via Groups + Cross.
The insane bit CamSync does not care that one Parallel lives on a 1×2×1 cell and another store sits on cam 3 of a 2×4×5. Name both endpoints; if Rows×Cols match, they blend. Volumetric layout is configuration — not a built-in grid type inside CamSync.

Knobs

FieldMeaning
Enabled / Alpha Soft pull w ← (1−α)·w + α·mean. 0.01 = 1%, 1.0 = hard sync. Default α is 1 when Enabled and Alpha ≤ 0.
When SyncManual · SyncAfterSample · SyncAfterStep · SyncAfterPulse. Hooks fire from TrainStackMSE/CE and Pulse().
Groups Within one Parallel: cam-index cliques. Empty ⇒ one group of every branch. Example: {{0,1},{2,3}} syncs pairs only.
Cross []SyncPair of SyncEndpoint{StackIdx, Branch, Store} — same layer across cams, Dense stem ↔ matching cam Dense, or any same-shaped pair the Stack can resolve.

What works / what does not (yet)

  • ✅ All cams together, selective groups, soft or hard α
  • ✅ Cross-layer / cross-cam pairs inside one Stack
  • ✅ “Same layer on a different mesh” when weight shapes match
  • v1.1.1 — cam adjusting on all layer types (Dense, CNN, Parallel, Stack-resolved stores)
  • ❌ One-directional (teacher → student) — pairs are mutual mean-blend
  • ❌ Mismatched Rows×Cols — rejected or skipped
  • ❌ Auto sync across two separate Stack instances — host calls weights.BlendStores on the two stores

API

  • parallel.CamSyncConfig · DefaultCamSync() · SetCamSync on Layer or Stack
  • SyncNow / Pulse / MaybeSync
  • weights.BlendStores · weights.StoreCosine (diagnostics)

Cameral sandwiches: §68. Parallel package: §27. Host probe: ok/cam_sync (MNIST sample α×cams matrix).

Go example

examples/70-cam-sync/main.go

Run:cd welvet/examples/70-cam-sync && source ../env.sh && go run .
package main

import (
	"fmt"

	"github.com/openfluke/welvet/core"
	"github.com/openfluke/welvet/layers/dense"
	"github.com/openfluke/welvet/layers/parallel"
	"github.com/openfluke/welvet/quant"
	"github.com/openfluke/welvet/stub/seed"
	"github.com/openfluke/welvet/weights"
)

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)
	left := hemi.Branches[0].(*dense.Layer).Weights
	right := hemi.Branches[1].(*dense.Layer).Weights
	_ = seed.InitStoreHe(left, 16, seed.From("cam0"))
	_ = seed.InitStoreHe(right, 16, seed.From("cam1"))

	hemi.SetCamSync(parallel.CamSyncConfig{
		Enabled: true,
		Alpha:   1.0,
		When:    parallel.SyncManual,
	})
	before, _ := weights.StoreCosine(left, right)
	if err := hemi.SyncNow(); err != nil {
		panic(err)
	}
	after, _ := weights.StoreCosine(left, right)
	fmt.Printf("cosine before=%.4f after=%.4f\n", before, after)
}

Output

exit 0 · last run via go run .

cosine before=-0.0697 after=1.0000