Skip to content

Commit

Permalink
Merge branch 'release/v0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Wrede committed Oct 25, 2023
2 parents e4731f7 + 6d0b7ff commit f801a27
Show file tree
Hide file tree
Showing 413 changed files with 4,801 additions and 5,726 deletions.
2 changes: 1 addition & 1 deletion .ci/tests/examples/configure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -e

# Parse example name
if [ "$#" -ne 2 ]; then
>&2 echo "Wrong number of arguments (usage: run.sh <example-name>)"
>&2 echo "Wrong number of arguments (usage: configure.sh <example-name>)"
exit 1
fi
example="$1"
Expand Down
35 changes: 35 additions & 0 deletions .ci/tests/examples/inference_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sys
from time import sleep

import pymongo

N_CLIENTS = 2
RETRIES = 18
SLEEP = 10


def _eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)


def _wait_n_rounds(collection):
n = 0
for _ in range(RETRIES):
query = {'type': 'INFERENCE'}
n = collection.count_documents(query)
if n == N_CLIENTS:
return n
_eprint(f'Succeded cleints {n}. Sleeping for {SLEEP}.')
sleep(SLEEP)
_eprint(f'Succeded clients: {n}. Giving up.')
return n


if __name__ == '__main__':
# Connect to mongo
client = pymongo.MongoClient("mongodb://fedn_admin:password@localhost:6534")

# Wait for successful rounds
succeded = _wait_n_rounds(client['fedn-test-network']['control']['status'])
assert(succeded == N_CLIENTS) # check that all rounds succeeded
_eprint(f'Succeded inference clients: {succeded}. Test passed.')
19 changes: 19 additions & 0 deletions .ci/tests/examples/run_inference.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -e

# Parse example name
if [ "$#" -lt 1 ]; then
>&2 echo "Wrong number of arguments (usage: run_infrence.sh <example-name>)"
exit 1
fi
example="$1"

>&2 echo "Run inference"
pushd "examples/$example"
curl -k -X POST https://localhost:8090/infer

>&2 echo "Checking inference success"
".$example/bin/python" ../../.ci/tests/examples/inference_test.py

>&2 echo "Test completed successfully"
popd
2 changes: 1 addition & 1 deletion .ci/tests/examples/wait_for.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _retry(try_func, **func_args):
def _test_rounds(n_rounds):
client = pymongo.MongoClient(
"mongodb://fedn_admin:password@localhost:6534")
collection = client['fedn-test-network']['control']['round']
collection = client['fedn-network']['control']['rounds']
query = {'reducer.status': 'Success'}
n = collection.count_documents(query)
client.close()
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/code-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ jobs:
--skip .venv
--skip .mnist-keras
--skip .mnist-pytorch
--skip fedn_pb2.py
--skip fedn_pb2_grpc.py
- name: check Python formatting
run: >
.venv/bin/autopep8 --recursive --diff
--exclude .venv
--exclude .mnist-keras
--exclude .mnist-pytorch
--exclude fedn_pb2.py
--exclude fedn_pb2_grpc.py
.
- name: run Python linter
run: >
.venv/bin/flake8 .
--exclude ".venv,.mnist-keras,.mnist-pytorch,fedn_pb2.py"
--exclude ".venv,.mnist-keras,.mnist-pytorch,fedn_pb2.py,fedn_pb2_grpc.py"
- name: check for floating imports
run: >
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ jobs:
strategy:
matrix:
to_test:
- "mnist-keras keras"
- "mnist-pytorch pytorch"
- "mnist-keras kerashelper"
- "mnist-pytorch pytorchhelper"
python_version: ["3.8", "3.9","3.10"]
os:
- ubuntu-20.04
- ubuntu-22.04
- macos-11
runs-on: ${{ matrix.os }}
steps:
- name: checkout
Expand All @@ -38,7 +37,10 @@ jobs:
- name: run ${{ matrix.to_test }}
run: .ci/tests/examples/run.sh ${{ matrix.to_test }}
if: ${{ matrix.os != 'macos-11' }} # skip Docker part for MacOS

- name: run ${{ matrix.to_test }} inference
run: .ci/tests/examples/run_inference.sh ${{ matrix.to_test }}
if: ${{ matrix.os != 'macos-11' && matrix.to_test == 'mnist-keras keras' }} # example available for Keras

- name: print logs
if: failure()
Expand Down
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,4 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

2 changes: 1 addition & 1 deletion config/settings-client.yaml.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
network_id: fedn-test-network
network_id: fedn-network
discover_host: reducer
discover_port: 8090
18 changes: 9 additions & 9 deletions config/settings-combiner.yaml.template
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
network_id: fedn-test-network
controller:
discover_host: reducer
discover_port: 8090
network_id: fedn-network
discover_host: reducer
discover_port: 8090

name: combiner
host: combiner
port: 12080
max_clients: 30


combiner:
name: combiner
host: combiner
port: 12080
max_clients: 30
7 changes: 1 addition & 6 deletions config/settings-reducer.yaml.template
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
network_id: fedn-test-network
token: fedn_token

control:
state: idle
helper: keras
network_id: fedn-network

statestore:
type: MongoDB
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ services:
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
entrypoint: [ "sh", "-c" ]
command:
- "/venv/bin/pip install --no-cache-dir -e /app/fedn && /venv/bin/fedn run combiner -in config/settings-combiner.yaml"
- "/venv/bin/pip install --no-cache-dir -e /app/fedn && /venv/bin/fedn run combiner --init config/settings-combiner.yaml"
ports:
- 12080:12080

Expand All @@ -110,6 +110,6 @@ services:
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
entrypoint: [ "sh", "-c" ]
command:
- "/venv/bin/pip install --no-cache-dir -e /app/fedn && /venv/bin/fedn run client -in config/settings-client.yaml"
- "/venv/bin/pip install --no-cache-dir -e /app/fedn && /venv/bin/fedn run client --init config/settings-client.yaml"
deploy:
replicas: 0
19 changes: 19 additions & 0 deletions examples/mnist-keras/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,22 @@ Finally, you can start the experiment from the "control" tab of the UI.

## Clean up
You can clean up by running `docker-compose down`.

## Connecting to a distributed deployment
To start and remotely connect a client with the required dependencies for this example, start by downloading the `client.yaml` file. You can either navigate the reducer UI or run the following command.

```bash
curl -k https://<reducer-fqdn>:<reducer-port>/config/download > client.yaml
```
> **Note** make sure to replace `<reducer-fqdn>` and `<reducer-port>` with appropriate values.
Now you are ready to start the client via Docker by running the following command.

```bash
docker run -d \
-v $PWD/client.yaml:/app/client.yaml \
-v $PWD/data:/var/data \
-e ENTRYPOINT_OPTS=--data_path=/var/data/mnist.npz \
ghcr.io/scaleoutsystems/fedn/fedn:develop-mnist-keras run client -in client.yaml
```
> **Note** If reducer and combiner host names, as specfied in the configuration files, are not resolvable in the client host network you need to use the docker option `--add-hosts` to make them resolvable. Please refer to the Docker documentation for more detail.
2 changes: 1 addition & 1 deletion examples/mnist-keras/bin/init_venv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -e

# Init venv
python -m venv .mnist-keras
python3 -m venv .mnist-keras

# Pip deps
.mnist-keras/bin/pip install --upgrade pip
Expand Down
49 changes: 39 additions & 10 deletions examples/mnist-keras/client/entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import fire
import numpy as np
import tensorflow as tf

from fedn.utils.kerashelper import KerasHelper
from fedn.utils.helpers import get_helper, save_metadata, save_metrics

HELPER_MODULE = 'kerashelper'
NUM_CLASSES = 10


Expand All @@ -17,7 +18,6 @@ def _get_data_path():
client = docker.from_env()
container = client.containers.get(os.environ['HOSTNAME'])
number = container.name[-1]

# Return data path
return f"/var/data/clients/{number}/mnist.npz"

Expand Down Expand Up @@ -64,8 +64,8 @@ def _load_data(data_path, is_train=True):

def init_seed(out_path='seed.npz'):
weights = _compile_model().get_weights()
helper = KerasHelper()
helper.save_model(weights, out_path)
helper = get_helper(HELPER_MODULE)
helper.save(weights, out_path)


def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1):
Expand All @@ -74,16 +74,26 @@ def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1

# Load model
model = _compile_model()
helper = KerasHelper()
weights = helper.load_model(in_model_path)
helper = get_helper(HELPER_MODULE)
weights = helper.load(in_model_path)
model.set_weights(weights)

# Train
model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs)

# Save
weights = model.get_weights()
helper.save_model(weights, out_model_path)
helper.save(weights, out_model_path)

# Metadata needed for aggregation server side
metadata = {
'num_examples': len(x_train),
'batch_size': batch_size,
'epochs': epochs,
}

# Save JSON metadata file
save_metadata(metadata, out_model_path)


def validate(in_model_path, out_json_path, data_path=None):
Expand All @@ -93,8 +103,8 @@ def validate(in_model_path, out_json_path, data_path=None):

# Load model
model = _compile_model()
helper = KerasHelper()
weights = helper.load_model(in_model_path)
helper = get_helper(HELPER_MODULE)
weights = helper.load(in_model_path)
model.set_weights(weights)

# Evaluate
Expand All @@ -111,15 +121,34 @@ def validate(in_model_path, out_json_path, data_path=None):
"test_accuracy": model_score_test[1],
}

# Save JSON
save_metrics(report, out_json_path)


def infer(in_model_path, out_json_path, data_path=None):
# Using test data for inference but another dataset could be loaded
x_test, _ = _load_data(data_path, is_train=False)

# Load model
model = _compile_model()
helper = get_helper(HELPER_MODULE)
weights = helper.load(in_model_path)
model.set_weights(weights)

# Infer
y_pred = model.predict(x_test)
y_pred = np.argmax(y_pred, axis=1)

# Save JSON
with open(out_json_path, "w") as fh:
fh.write(json.dumps(report))
fh.write(json.dumps({'predictions': y_pred.tolist()}))


if __name__ == '__main__':
fire.Fire({
'init_seed': init_seed,
'train': train,
'validate': validate,
'infer': infer,
'_get_data_path': _get_data_path, # for testing
})
4 changes: 3 additions & 1 deletion examples/mnist-keras/client/fedn.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ entry_points:
train:
command: /venv/bin/python entrypoint train $ENTRYPOINT_OPTS
validate:
command: /venv/bin/python entrypoint validate $ENTRYPOINT_OPTS
command: /venv/bin/python entrypoint validate $ENTRYPOINT_OPTS
infer:
command: /venv/bin/python entrypoint infer $ENTRYPOINT_OPTS
Loading

0 comments on commit f801a27

Please sign in to comment.