Skip to content

Commit 9678042

Browse files
author
Andreas Hellander
committed
Added load test based on shuffling numpy arrays
1 parent eeb82ce commit 9678042

9 files changed

+250
-2
lines changed

examples/async-simulation/docker-compose.override.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ services:
66
client:
77
build:
88
args:
9-
REQUIREMENTS: examples/async-simulation/requirements.txt
9+
REQUIREMENTS: examples/load-test/requirements.txt
1010
deploy:
1111
replicas: 2
1212
volumes:
1313
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
14-
- ${HOST_REPO_DIR:-.}/examples/async-simulation/data:/var/data
14+
- ${HOST_REPO_DIR:-.}/examples/load-test/data:/var/data
1515
- /var/run/docker.sock:/var/run/docker.sock

examples/load-test/.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
data
2+
*.npz
3+
*.tgz
4+
*.tar.gz
5+
.async-simulation
6+
client.yaml

examples/load-test/README.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# LOAD TEST
2+
This example can be used as a load test for FEDn.
3+
4+
No actual machine learning is being done - the clients generate a
5+
random array of a configurable size. In this way a developer can
6+
test the performance / scalability of a given FEDn network in a flexible
7+
way simply by shuffling around and aggregating numeric arrays.
8+
9+
## Prerequisites
10+
- [Python 3.8, 3.9 or 3.10](https://www.python.org/downloads)
11+
- [Docker](https://docs.docker.com/get-docker)
12+
- [Docker Compose](https://docs.docker.com/compose/install)
13+
14+
## Running the example (pseudo-distributed, single host)
15+
16+
Clone FEDn and locate into this directory.
17+
```sh
18+
git clone https://github.com/scaleoutsystems/fedn.git
19+
cd fedn/examples/load-test
20+
```
21+
22+
### Preparing the environment, the local data, the compute package and seed model
23+
24+
Install FEDn:
25+
```
26+
pip install fedn
27+
```
28+
29+
Standing in examples/load-test
30+
```
31+
pip install -r requirements.txt
32+
```
33+
34+
Create the compute package and a seed model that you will be asked to upload in the next step.
35+
```
36+
tar -czvf package.tgz client
37+
```
38+
39+
```
40+
python client/entrypoint init_seed
41+
```
42+
43+
### Deploy FEDn and two clients
44+
docker-compose -f ../../docker-compose.yaml -f docker-compose.override.yaml up
45+
46+
### Initialize the FEDn network
47+
Edit 'init_fedn.py' to configure the FEDn host (controller) to connect to, then
48+
```
49+
python init_fedn.py
50+
```
51+
52+
Launch clients
53+
> **Note**: run with `--scale client=N` to start *N* clients.
54+
55+
## Clean up
56+
You can clean up by running `docker-compose down -v`.

examples/load-test/client/entrypoint

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# /bin/python
2+
import time
3+
4+
import fire
5+
import numpy as np
6+
7+
from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics
8+
9+
HELPER_MODULE = 'numpyhelper'
10+
ARRAY_SIZE = 1000000
11+
12+
13+
def save_model(weights, out_path):
14+
""" Save model to disk.
15+
16+
:param model: The model to save.
17+
:type model: torch.nn.Module
18+
:param out_path: The path to save to.
19+
:type out_path: str
20+
"""
21+
helper = get_helper(HELPER_MODULE)
22+
helper.save(weights, out_path)
23+
24+
25+
def load_model(model_path):
26+
""" Load model from disk.
27+
28+
param model_path: The path to load from.
29+
:type model_path: str
30+
:return: The loaded model.
31+
:rtype: torch.nn.Module
32+
"""
33+
helper = get_helper(HELPER_MODULE)
34+
weights = helper.load(model_path)
35+
return weights
36+
37+
38+
def init_seed(out_path='seed.npz'):
39+
""" Initialize seed model.
40+
41+
:param out_path: The path to save the seed model to.
42+
:type out_path: str
43+
"""
44+
# Init and save
45+
weights = [np.random.rand(1, ARRAY_SIZE)]
46+
save_model(weights, out_path)
47+
48+
49+
def train(in_model_path, out_model_path):
50+
""" Train model.
51+
52+
"""
53+
54+
# Load model
55+
weights = load_model(in_model_path)
56+
57+
# Train
58+
time.sleep(np.random.randint(4, 15))
59+
60+
# Metadata needed for aggregation server side
61+
metadata = {
62+
'num_examples': ARRAY_SIZE,
63+
}
64+
65+
# Save JSON metadata file
66+
save_metadata(metadata, out_model_path)
67+
68+
# Save model update
69+
save_model(weights, out_model_path)
70+
71+
72+
def validate(in_model_path, out_json_path):
73+
""" Validate model.
74+
75+
:param in_model_path: The path to the input model.
76+
:type in_model_path: str
77+
:param out_json_path: The path to save the output JSON to.
78+
:type out_json_path: str
79+
:param data_path: The path to the data file.
80+
:type data_path: str
81+
"""
82+
weights = load_model(in_model_path)
83+
84+
# JSON schema
85+
report = {
86+
"mean": np.mean(weights),
87+
}
88+
89+
# Save JSON
90+
save_metrics(report, out_json_path)
91+
92+
93+
if __name__ == '__main__':
94+
fire.Fire({
95+
'init_seed': init_seed,
96+
'train': train,
97+
'validate': validate
98+
})

examples/load-test/client/fedn.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
entry_points:
2+
train:
3+
command: /venv/bin/python entrypoint train $ENTRYPOINT_OPTS
4+
validate:
5+
command: /venv/bin/python entrypoint validate $ENTRYPOINT_OPTS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Compose schema version
2+
version: '3.3'
3+
4+
# Overriding requirements
5+
services:
6+
client:
7+
build:
8+
args:
9+
REQUIREMENTS: examples/async-simulation/requirements.txt
10+
deploy:
11+
replicas: 2
12+
volumes:
13+
- ${HOST_REPO_DIR:-.}/fedn:/app/fedn
14+
- ${HOST_REPO_DIR:-.}/examples/async-simulation/data:/var/data
15+
- /var/run/docker.sock:/var/run/docker.sock

examples/load-test/init_fedn.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from fedn import APIClient
2+
3+
DISCOVER_HOST = '127.0.0.1'
4+
DISCOVER_PORT = 8092
5+
6+
client = APIClient(DISCOVER_HOST, DISCOVER_PORT)
7+
client.set_package('package.tgz', 'numpyhelper')
8+
client.set_initial_model('seed.npz')

examples/load-test/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fire==0.3.1

examples/load-test/run_clients.py

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""This scripts starts N_CLIENTS using the SDK.
2+
3+
If you are running with a local deploy of FEDn
4+
using docker compose, you need to make sure that clients
5+
are able to resolve the name "combiner" to 127.0.0.1
6+
7+
One way to accomplish this is to edit your /etc/host,
8+
adding the line:
9+
10+
combiner 127.0.0.1
11+
12+
"""
13+
14+
15+
import copy
16+
import time
17+
18+
from fedn import APIClient
19+
from fedn.network.clients.client import Client
20+
21+
DISCOVER_HOST = '127.0.0.1'
22+
DISCOVER_PORT = 8092
23+
N_CLIENTS = 5
24+
CLIENTS_AVAILABLE_TIME = 120
25+
26+
config = {'discover_host': DISCOVER_HOST, 'discover_port': DISCOVER_PORT, 'token': None, 'name': 'testclient',
27+
'client_id': 1, 'remote_compute_context': True, 'force_ssl': False, 'dry_run': False, 'secure': False,
28+
'preshared_cert': False, 'verify': False, 'preferred_combiner': False,
29+
'validator': True, 'trainer': True, 'init': None, 'logfile': 'test.log', 'heartbeat_interval': 2,
30+
'reconnect_after_missed_heartbeat': 30}
31+
32+
if __name__ == '__main__':
33+
34+
# Start up N_CLIENTS clients
35+
clients = []
36+
for i in range(N_CLIENTS):
37+
config_i = copy.deepcopy(config)
38+
config['name'] = 'client{}'.format(i)
39+
clients.append(Client(config))
40+
41+
# Run a session
42+
client = APIClient(DISCOVER_HOST, DISCOVER_PORT)
43+
44+
session_config_fedavg = {
45+
"helper": "numpyhelper",
46+
"session_id": str(uuid.uuid4()),
47+
"aggregator": "fedavg",
48+
"round_timeout": 30,
49+
"rounds": 5,
50+
}
51+
52+
result_fedavg = client.start_session(**session_config_fedavg)
53+
while not client.session_is_finished(session_id):
54+
time.sleep(2)
55+
56+
# Disconnect clients
57+
time.sleep(CLIENTS_AVAILABLE_TIME)
58+
for client in clients:
59+
client.detach()

0 commit comments

Comments
 (0)