Authors1: El-Mahdi El-Mhamdi, Rachid Guerraoui, Sébastien Rouault
This is the code used in the experiments of Distributed Momentum for Byzantine-resilient Stochastic Gradient Descent (ICLR 2021), co-authored by El-Mahdi El-Mhamdi, Rachid Guerraoui and Sébastien Rouault.
1alphabetical order, as for all the papers from the Distributed Computing Laboratory (DCL) of EPFL.
Reproducing the results (see the paper)
Python 3.7.3 has been used to run our scripts.
Besides the standard libraries associated with Python 3.7.3, our scripts have been tested with2:
Library | Version |
---|---|
numpy | 1.19.1 |
torch | 1.6.0 |
torchvision | 0.7.0 |
pandas | 1.1.0 |
matplotlib | 3.0.2 |
PIL | 7.2.0 |
requests | 2.21.0 |
urllib3 | 1.24.1 |
chardet | 3.0.4 |
certifi | 2018.08.24 |
idna | 2.6 |
six | 1.15.0 |
pytz | 2020.1 |
dateutil | 2.6.1 |
pyparsing | 2.2.0 |
cycler | 0.10.0 |
kiwisolver | 1.0.1 |
cffi | 1.13.2 |
2this list was automatically generated (see get_loaded_dependencies()
in tools/misc.py
).
Some dependencies depend on others, and some dependencies are optional (e.g., only used to process the results and produce the plots).
We list below the OS on which our scripts have been tested:
- Debian 10 (GNU/Linux 4.19.171-2 x86_64)
- Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-128-generic x86_64)
Although our experiments are time-agnostic, we list below the hardware components used:
- 1 Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
- 2 Nvidia GeForce GTX 1080 Ti
- 64 GB of RAM
Our results are reproducible in one command. In the root directory:
$ python3 reproduce.py
On our hardware, reproducing the results takes a bit less than a week.
Please be aware this script will require non-negligible disk space: 2.1 GB of run data, and 132 MB of graphs.
Depending on the hardware, instructing the script to launch several runs per available GPU may reduce the total runtime. For instance, to push up to 4 concurrent runs per GPU:
$ python3 reproduce.py --supercharge 4
If you are using algorithms, results or code from this paper, you can include in your bibliography:
@inproceedings{distributed-momentum,
author = {El-Mahdi El-Mhamdi and
Rachid Guerraoui and
S\'{e}bastien Rouault},
title = {Distributed Momentum for Byzantine-resilient Stochastic Gradient Descent},
booktitle = {9th International Conference on Learning Representations, {ICLR} 2021,
Vienna, Austria, May 4–8, 2021},
publisher = {OpenReview.net},
year = {2021},
url = {https://openreview.net/forum?id=H8UHdhWG6A3},
}
attack.py
can be used to simulate reproducible3 distributed stochastic gradient descent sessions,
with several customizations (besides number of workers, batch size, learning rate evolution, choice of loss/regularization, etc):
- which gradient aggregation rule (GAR) is used instead of the standard average
- GAR-specific additional parameters can be specified
- which attack is followed by the adversary
- attack-specific additional parameters can be specified
- momentum applied at 3 different positions:
- at update (called at server in the paper), the standard
- at the workers (see the paper)
- at the server, a variant of at the workers where the server keeps the momentum
- additional features:
- gradient clipping (upper bound on the norm of the sampled, correct gradients)
- multiple local steps (instead of one) between communication
- checkpointing for training continuation4
3reasonable efforts to remove sources of non-determinism have been made, please see PyTorch's documentation for further details.
4reproducibility is currently not guaranteed when continuing experiments (PRNG/dataset loader states are not saved in checkpoints).
When measurement is enabled (i.e., --result-directory
has been specified), 17 metrics are captured for each step:
- variances between gradients
- sampled for the variance between the sampled, correct gradients
- honest for the variance between the correct gradients as received by the server, which can be different than sampled when momentum is computed at the workers
- attack for the variance between the adversarial gradients (usually 0 since the Byzantine workers usually send the same gradient)
- average norms of gradients
- sampled for the average norm of the sampled, correct gradients (and after optional clipping)
- honest for the average norm of the correct gradients as received by the server, which can be different than sampled when momentum is computed at the workers
- attack for the average norm of the adversarial gradients
- defense for the norm of the gradient computed by the GAR
- maximum coordinates of average gradients
- ditto as for average norms (i.e.: sampled, honest, attack, defense)
- cosine angles between average gradients (for each of the 6 possible pairs)
- sampled-honest, sampled-attack, sampled-defense
- honest-attack, honest-defense
- attack-defense
and 5 additional metrics:
- cumulative (until the current step) number of training points collectively consumed by the correct workers
- average loss observed by the correct worker
- l2-norm of the parameters from the initial parameters
- cosine angle between the average of the sampled, clipped, correct gradients and the one of the previous step
- the composite curvature (i.e. the quantity st in the paper) using
--nb-for-study-past
past gradients for computation
The result directory will contain:
- the output metrics for each step formatted in mere CSV, with a header starting with character "#"
- human-readable and machine-readable (JSON) information on the run made
- standard and error outputs of
attack.py
study.py
can help process these result directories.
This tool can compute additional metrics (e.g., for which steps the theoretical requirements on the variance-norm ratio was satisfied),
and plot graphs.
See reproduce.py
for relevant uses of attack.py
, and study.py
(which is to be used as a library).
Otherwise the command-line help (python3 attack.py --help
) can get you a glimpse of how to use the tool.
For setup testing purpose, just run python3 attack.py
.
The standard output will contain information on the running defaults.
It should be easy to add new GARs.
In directory aggregators
:
- copy-paste
template.py
with a new name - fill in the
aggregate
,check
and (optionally) theupper_bound
functions;
the interface of these functions is specified in the docstring of__init__.py
- use your new GAR with the
--gar
command-line argument ofattack.py
;
arbitrary, additional keyword-arguments can be passed with--gar-args
It should be easy to add new attacks.
In directory attacks
:
- copy-paste
template.py
with a new name - fill in the
attack
andcheck
functions;
the interface of these functions is specified in the docstring of__init__.py
- use your new attack with the
--attack
command-line argument ofattack.py
;
arbitrary, additional keyword-arguments can be passed with--attack-args
Following the same principle, new datasets can be added in experiments/datasets
.