A research testbed built on the current Flower (flwr) app-model: a federated LoRA fine-tune of a tiny BERT over non-IID data, aggregating only the adapters, with every round and client emitted as OpenTelemetry traces and metrics.
Phalanx is a federated-learning research testbed that rides the latest Flower release: the flwr Message API, flwr-datasets, HuggingFace Transformers, and PEFT/LoRA. Its distinguishing feature is OpenTelemetry-native observability. The server emits a span and FL metrics for every round; each client emits a span for its local train and evaluate passes. You can watch a federated run in any OTLP backend (Jaeger, Grafana Tempo, an OpenTelemetry Collector), which stock Flower does not provide out of the box.
The default showcase is a federated LoRA fine-tune of Google's tiny BERT on IMDB sentiment, partitioned non-IID with a Dirichlet partitioner. Only the LoRA adapters and the classification head are federated; the frozen backbone stays on each client, so the payload on the wire is small.
$ make trace # local simulation, traces printed to the console
[ROUND 1/3]
aggregate_train: Received 2 results and 0 failures
aggregate_evaluate: Received 2 results and 0 failures
-> Aggregated MetricRecord: {'loss': 0.67, 'accuracy': 0.62}
# plus an `fl.round` OTel span (fl.round, fl.loss, fl.accuracy, fl.clients) per round,
# and an `fl.client.{train,evaluate}` span per participating client.
git clone https://github.com/ajbarea/phalanx-fl.git
cd phalanx-fl
make sync # install deps (CPU torch + HF stack + dev tools)
make smoke # fast 2-round federated simulation
make trace # run with OTel traces printed to the console (no collector needed)make run runs the full simulation, make test runs the suite, make lint runs ruff + ty. Run make with no target for the full list.
flwr 1.31 keeps simulation and federation settings in ~/.flwr/config.toml, not in pyproject.toml. The [tool.flwr.federations.local-simulation] block shipped in pyproject.toml is migrated there automatically on your first flwr run (flwr then comments the local copy out; see flwr#6824). Override the federation size per run without editing any file:
uv run flwr run . local-simulation --federation-config 'options.num-supernodes=10'By default telemetry is recorded but not exported (no connection noise). Point it at a collector to export traces and metrics over OTLP:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
make runOr print spans to the terminal with OTEL_TRACES_EXPORTER=console (this is what make trace does).
A standard Flower app-model layout:
| Module | Role |
|---|---|
phalanx/task.py |
Model (HF transformer + PEFT/LoRA), data (flwr-datasets, IID or Dirichlet non-IID), train/eval, adapter-state helpers |
phalanx/client_app.py |
ClientApp: loads broadcast adapters, trains locally, replies with adapters only; wraps each pass in a client span |
phalanx/server_app.py |
ServerApp + ObservableFedAvg: FedAvg over adapters; emits an fl.round span + aggregated loss/accuracy/participation metrics each round |
phalanx/telemetry.py |
OpenTelemetry layer: tracer/meter providers, round/client spans, FL metrics; OTLP / console / in-memory exporters |
Stack: Flower (Message API + Simulation Engine) · PyTorch + HuggingFace Transformers + PEFT/LoRA · flwr-datasets · OpenTelemetry · uv + Ruff + ty
Run config lives in pyproject.toml under [tool.flwr.app.config], overridable per run with --run-config:
| Key | Default | Meaning |
|---|---|---|
num-server-rounds |
3 |
number of FL rounds |
model-name |
google/bert_uncased_L-2_H-128_A-2 |
HF model (tiny BERT) |
dataset |
stanfordnlp/imdb |
HF dataset |
num-labels |
2 |
classification labels |
partitioner |
dirichlet |
dirichlet (non-IID) or iid |
dirichlet-alpha |
0.5 |
lower means more label skew |
local-epochs |
1 |
local epochs per round |
fraction-train / fraction-evaluate |
0.1 |
client sampling fractions |
otel-service-name |
phalanx-fl |
OTel service.name resource attribute |
uv run flwr run . local-simulation --run-config 'num-server-rounds=5 partitioner=iid'Full docs at ajbarea.github.io/phalanx-fl, built with Zensical.
Greek φάλαγξ: the infantry formation where many soldiers advance as one disciplined line, each shield interlocked with the next. That is the shape of federated learning here. Many clients train in step and their updates aggregate into a single model that moves as one. The observability layer is the vantage point above the formation, where every unit and every round is in view.