Skip to content

Getting Started

Prerequisites

  • uv (Python package manager)
  • Python 3.12 or 3.13

No GPU is required. PyTorch comes from the CPU index on every platform except Linux aarch64, which gets the CUDA 13 build for the GH200 nodes. That build runs on CPU too, at the cost of about 2 GB of CUDA wheels; Docker on Apple Silicon is Linux aarch64.

Install

git clone https://github.com/ajbarea/phalanx-fl.git
cd phalanx-fl
make sync        # uv sync --all-extras (torch + HF stack + experiments + dev tools)

Run a federated simulation

make smoke       # fast 2-round run (sanity check)
make run         # full run (uses num-server-rounds from pyproject)
make trace       # run with OpenTelemetry traces printed to the console

The first run downloads the model (google/bert_uncased_L-2_H-128_A-2, ~18 MB) and the IMDB dataset, then trains on CPU. Subsequent runs reuse the cache.

Federation setup (flwr 1.42)

Federation settings live outside pyproject.toml: the SuperLink connection belongs to the Flower config (~/.flwr/config.toml, or $FLWR_HOME), and Simulation Runtime settings are SuperLink state. A [tool.flwr.federations] block left in pyproject.toml is migrated out on the first flwr run, rewriting the file in place (flwr#6824).

The Makefile names the built-in local connection and passes the settings per run, so a clone reproduces the default five-node federation with no bootstrap step. Override it for a single run:

uv run flwr run . local --federation-config 'num-supernodes=10 client-resources-num-cpus=2'

Override app run-config (rounds, partitioner, model) similarly:

uv run flwr run . local --run-config 'num-server-rounds=5 partitioner="iid"'

Observability

Telemetry is recorded by default but not exported (no collector required, no connection noise). compose.yaml holds two local backends, pinned by digest and bound to localhost:

make otel-up                      # Jaeger: traces, UI at http://localhost:16686
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_METRICS_EXPORTER=none  # Jaeger stores traces only; metrics would fail to export
make run

Jaeger stores traces only. For metrics too, run make otel-aspire, export to http://localhost:18889 and leave OTEL_METRICS_EXPORTER unset; the Aspire Dashboard UI is at http://localhost:18888, and its login token is in docker compose logs aspire. Its OTLP endpoint accepts telemetry without authentication, which is why both services bind to localhost only. make otel-down stops both. Any other OTLP collector works the same way: set OTEL_EXPORTER_OTLP_ENDPOINT to its gRPC port.

To print spans to the terminal instead (no backend needed):

OTEL_TRACES_EXPORTER=console make run    # this is what `make trace` does

Each round produces an fl.round span (attributes: fl.round, fl.loss, fl.accuracy, fl.global_loss, fl.global_accuracy, fl.train_clients, fl.evaluate_clients, fl.train_ess, fl.evaluate_ess, fl.failures) and the matching fl.round.* metrics; each participating client produces an fl.client.train or fl.client.evaluate span and fl.client.* metrics.

Develop

make lint        # ruff format --check + ruff check + ty + actionlint
make test        # pytest
make audit       # pip-audit