A federated learning platform that enables multiple institutes to collaboratively fine-tune a shared LLM without ever sharing their private data. Each institute trains locally; only model updates travel over the network. A central department node orchestrates the federation, tracks experiments, and manages model versions.
The system is split into two logical tiers.
Hosts the federation orchestrator, experiment tracking, institute registry, and the shared frontend.
Each institute runs its own stack: local data, inference, chat, and a Flower SuperNode that connects back to the department SuperLink.
Make sure the following tools are installed on your machine before proceeding.
| Tool | Purpose | Install |
|---|---|---|
| Docker (with Compose v2) | Run all services | docs.docker.com |
| uv | Python package manager (replaces pip/venv) | docs.astral.sh/uv |
| Node.js + npm | Frontend development | nodejs.org |
| NVIDIA GPU + drivers | Required for training and inference containers | nvidia.com/drivers |
| NVIDIA Container Toolkit | Expose GPU to Docker | docs.nvidia.com/datacenter/cloud-native |
decentralised-ai/
├── department/ # Department-side microservices
│ ├── federated-learning-management-service/
│ ├── institute-service/
│ └── mlflow-service/
├── institute/ # Institute-side microservices
│ ├── chat-service/
│ ├── data-service/
│ ├── inference-service/
│ ├── model-service/
│ └── nginx-service/
├── federated-learning-service/ # Flower ClientApp + ServerApp
├── frontend/ # React + TypeScript + Vite web UI
├── shared-auth-library/ # Shared JWT/OIDC library
├── misc/ # Utility scripts (model downloader)
├── models/ # Local model storage (gitignored)
│ ├── department/ # Department model files
│ └── institute/ # Per-institute model files
├── docker/ # Docker Compose files
├── deployment/ # Deployment scripts and API sync tools
├── keycloak-initial-configuration/ # Keycloak realm import files
└── docs/ # Documentation and screenshots
Do this first. The model files are large (~13 GB) and are not included in the repository.
cd ./miscInstall dependencies:
uv syncCreate the .env file from the template and fill in your values:
cp .env.template .envHUGGINGFACE_TOKEN=hf_... # Your Hugging Face access token
MODEL_PATH=../models/department # Where the model will be saved
HF_MODEL_ID=meta-llama/Llama-2-7b-chat-hfHugging Face setup:
- Create an account at huggingface.co and generate an access token at huggingface.co/settings/tokens.
- Request access to the model at huggingface.co/meta-llama/Llama-2-7b-chat-hf. Meta will approve the request.
Download the model:
uv run --env-file .env src/huggingface_model_downloader.pyRemove symlinks so Docker can bind-mount the files correctly (your snapshot hash will differ):
./model_remove_symlinks.sh ../models/department/original/models--meta-llama--Llama-2-7b-chat-hf/snapshots/<snapshot-hash>After the download and symlink removal, ./models/department/ should contain:
models/department/llama-2-7b/
└── base/
├── config.json
├── generation_config.json
├── model.safetensors.index.json
├── model-00001-of-00002.safetensors
├── model-00002-of-00002.safetensors
├── special_tokens_map.json
├── tokenizer.json
└── tokenizer_config.json
The fastest way to bring up the full system. All services run as Docker containers.
Ensure shared-auth-library is published to the GitLab registry
All Python services pull shared-auth-library from the internal GitLab PyPI registry when running in Docker. Make sure it has been published before building any image. See shared-auth-library/README.md.
Also verify that every service's pyproject.toml has the GitLab index active (not the local path):
[tool.uv.sources]
# shared-auth-library = { path = "../../shared-auth-library" } ← must be commented out
shared-auth-library = { index = "gitlab" }Build and install the Flower FAB
The FL Management Service needs the federated learning app pre-installed before it can start fine-tuning jobs. Do this once (and again whenever federated-learning-service changes):
# 1. Build the FAB from the federated-learning-service
cd federated-learning-service
uv sync
flwr buildThis produces a file named luca-fanto.federated-learning-service.<version>.fab in the current directory.
# 2. Copy the .fab into the FL management service
cp luca-fanto.federated-learning-service.*.fab \
../department/federated-learning-management-service/flwr/fab/# 3. Install the FAB so Flower can find the app
cd ../department/federated-learning-management-service/flwr
flwr install fab/luca-fanto.federated-learning-service.<version>.fab --flwr-dir .This creates the app directory under ./apps/luca-fanto.federated-learning-service.<version>/.
# 4. Copy the federated-learning-service pyproject.toml into the installed app
cp ../../../federated-learning-service/pyproject.toml \
apps/luca-fanto.federated-learning-service.<version>/pyproject.tomlReplace
<version>with the actual version string (e.g.0.1.0). After step 3 you can check the exact folder name withls apps/.
Sync the API clients remotely
The frontend Docker image is built with the TypeScript API clients pre-installed from the GitLab npm registry. Run the sync script to generate, publish, and install them before building any image:
cd deployment/apis
cp .env.template .env.config
# fill in UV_INDEX_GITLAB_USERNAME, UV_INDEX_GITLAB_PASSWORD, GITLAB_TOKEN
./sync-apis.sh
sync-apis.shstarts each service briefly to extract its OpenAPI spec, generates a TypeScript client withopenapi-generator, publishes it to the GitLab npm registry, and installs it intofrontend. Run it again whenever a service API changes.
Create the env file from the dev template:
cd deployment/department
cp .env.department.dev.docker.template .env.department.dev.dockerStart the department stack (builds images locally from source):
docker compose -f ../../docker/docker-compose.department.dev.yml \
--env-file .env.department.dev.docker up -dCreate the env file from the dev template (one per institute):
cd deployment/institute
cp .env.institute-INSTITUTE_NAME-REALM.docker.dev.template .env.institute-<REALM>.docker.devStart the institute stack (builds images locally from source):
docker compose -f ../../docker/docker-compose.institute.dev.yml \
--env-file .env.institute-<REALM>.docker.dev up -dRun each microservice locally for development. Services talk to each other over localhost; only the infrastructure (databases, Redis, Keycloak) runs in Docker.
All Python services depend on shared-auth-library. For local development you install it from a local path instead of the GitLab registry.
cd shared-auth-library
uv sync
uv buildSee shared-auth-library/README.md for details.
The frontend uses auto-generated TypeScript clients built from each service's OpenAPI spec. Run the sync script once before starting the frontend (and again whenever a service API changes):
cd deployment/apis
./sync-dev-apis.shStart the local Docker Compose for the department. This brings up Keycloak, Redis, and the two MySQL databases (no application services):
cd department/docker
cp .env.template .env
# fill in passwords
docker compose -f docker-compose.local.yml --env-file .env up -dOnce the infrastructure is up, Keycloak is available at http://localhost:8086. Log in with the admin credentials you set in .env (KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD).
You need to create a realm and import the spa-client into it so the frontend can authenticate.
Create the Department realm
- Open
http://localhost:8086and log in as admin. - Click the realm dropdown (top-left) → Create realm.
- Name it
Department(this must matchREALM_NAMEin each service's.env.dev).
Import the spa-client
- Inside the
Departmentrealm, go to Clients → Import client. - Upload the file
docs/keycloak-spa-client-configuration/example-department-spa-client.json. - Save.
The imported client is a public OpenID Connect client (spa-client) with:
- PKCE enabled (
S256) - Redirect URI and web origin set to
http://localhost:3000 - A hardcoded
realm_admin: trueclaim added to the access token (used by the department frontend to identify admin users)
For production, update the redirect URIs and web origins to match your actual frontend URL.
Create users
Add at least one user inside the Department realm so you can log in through the frontend.
Start each service individually. Click the links below to go to each service's README for the full setup steps:
Each service README explains how to:
- Switch
pyproject.tomlto use the localshared-auth-librarypath - Create the
.env.devfile from its template - Run
uv syncand start the service
cd institute/docker
cp .env.template .env
# fill in passwords
docker compose -f docker-compose.local.yml --env-file .env up -dThis starts Redis and the two MySQL databases (documents + chats).
Each institute has its own realm in the same Keycloak instance (running on the department node at http://localhost:8086).
Create the institute realm
- Open
http://localhost:8086and log in as admin. - Click the realm dropdown → Create realm.
- Name it after the institute (e.g.
ISIN). This must matchREALM_NAMEin the institute services'.env.devfiles.
Import the spa-client
- Inside the institute realm, go to Clients → Import client.
- Upload the file
docs/keycloak-spa-client-configuration/example-institute-spa-client.json. - Save.
The imported client is a public OpenID Connect client (spa-client) with:
- PKCE enabled (
S256) - Redirect URI and web origin set to
http://localhost:3000
The institute client has no
realm_adminclaim - that claim is department-only.
Create users
Add at least one user inside the institute realm so institute users can log in through the frontend.
Once all backend services are running:
The dev server starts at http://localhost:3000.
See docs/README_USE_PORTS.md for the full port registry.
For any Python service:
cd <service-directory>
uv sync
source .venv/bin/activate
uv run pytestFor the frontend:
cd frontend
npm install
npm run test
