feat: federated learning demo on the actor framework

- supervisor: ignore stale death messages (OneForAll restart storm),
  drop normally-exited children instead of respawning, add a
  restart-rate limit
- register actors before their thread starts so UUID dispatch and
  getSelf cannot race the spawn
- killActor now kills the thread so links see Killed
- fulfill connection promises with the pool winner on simultaneous
  connects
- clamp the PaySim test split on small datasets and split shared
  transaction types across trainers in non-IID partitioning
This commit is contained in:
2026-06-21 12:00:00 +02:00
parent 9a52fc5089
commit 711d26d9f7
25 changed files with 2127 additions and 115 deletions
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
#
# Demo: federated learning in P2P mode (full mesh, no central coordinator).
# Starts N peer nodes as separate processes; each trains locally, gossips
# updates via PeerSync (CRDT-gated rounds), and aggregates independently.
# At the end every peer must hold the SAME model — the script verifies the
# saved model files are byte-identical.
#
# Usage:
# scripts/demo-p2p.sh [ROUNDS] [PEERS] [EPOCHS] [/path/to/paysim.csv]
#
# Without a CSV path, nodes fall back to synthetic data (fast; the metrics are
# meaningless, but the gossip protocol and aggregation are fully exercised).
set -euo pipefail
ROUNDS="${1:-3}"
PEERS="${2:-3}"
EPOCHS="${3:-2}"
DATA="${4:-}"
BASE_PORT=9600
OUT="demo-out/p2p"
cd "$(dirname "$0")/.."
echo "==> Building..."
cabal build exe:fl-actors >/dev/null
BIN="$(cabal list-bin fl-actors)"
mkdir -p "$OUT"
rm -f "$OUT"/*.log fl_model_peer-*.bin
DATA_ARGS=()
[ -n "$DATA" ] && DATA_ARGS=(--data "$DATA")
PIDS=()
cleanup() { kill "${PIDS[@]}" 2>/dev/null || true; wait 2>/dev/null || true; }
trap cleanup EXIT INT TERM
for i in $(seq 0 $((PEERS - 1))); do
PORT=$((BASE_PORT + i))
ARGS=(--mode p2p --role peer --id "$i" --port "$PORT"
--rounds "$ROUNDS" --trainers "$PEERS" --epochs "$EPOCHS")
for j in $(seq 0 $((PEERS - 1))); do
[ "$j" = "$i" ] && continue
ARGS+=(--peer "localhost:$((BASE_PORT + j))")
done
echo "==> Starting peer-$i on port $PORT"
"$BIN" "${ARGS[@]}" "${DATA_ARGS[@]}" > "$OUT/peer$i.log" 2>&1 &
PIDS+=($!)
done
echo "==> Federation running; waiting for all $PEERS peers to finish..."
echo " (live log: tail -f $OUT/peer0.log)"
FAIL=0
for pid in "${PIDS[@]}"; do
wait "$pid" || FAIL=1
done
PIDS=()
if [ "$FAIL" -ne 0 ]; then
echo "!! A peer exited abnormally; check $OUT/ logs"
exit 1
fi
echo
echo "==> Evaluation results (peer-0's view):"
grep "Eval round=" "$OUT/peer0.log" || true
echo
MODELS=(fl_model_peer-*.bin)
if [ "${#MODELS[@]}" -ne "$PEERS" ]; then
echo "!! Expected $PEERS model files, found ${#MODELS[@]}; check $OUT/ logs"
exit 1
fi
FIRST="${MODELS[0]}"
for m in "${MODELS[@]:1}"; do
if ! cmp -s "$FIRST" "$m"; then
echo "!! Divergence: $m differs from $FIRST"
md5sum "${MODELS[@]}"
exit 1
fi
done
echo "==> Success: all $PEERS peers converged to a byte-identical model:"
ls -la "${MODELS[@]}"
echo " Full logs in $OUT/"