Mentions légales du service

Skip to content
Snippets Groups Projects
Verified Commit 7166e0a6 authored by ANDREY Paul's avatar ANDREY Paul
Browse files

Revise 'declearn-quickrun' backend to use asyncio.

parent 9ca21677
No related branches found
No related tags found
No related merge requests found
...@@ -30,6 +30,7 @@ The script then locally runs the FL experiment as layed out in the TOML file, ...@@ -30,6 +30,7 @@ The script then locally runs the FL experiment as layed out in the TOML file,
using privided model and data, and stores its result in the same folder. using privided model and data, and stores its result in the same folder.
""" """
import asyncio
import importlib import importlib
import logging import logging
import os import os
...@@ -53,7 +54,6 @@ from declearn.test_utils import make_importable ...@@ -53,7 +54,6 @@ from declearn.test_utils import make_importable
from declearn.utils import ( from declearn.utils import (
LOGGING_LEVEL_MAJOR, LOGGING_LEVEL_MAJOR,
get_logger, get_logger,
run_as_processes,
set_device_policy, set_device_policy,
) )
...@@ -61,19 +61,26 @@ __all__ = ["quickrun"] ...@@ -61,19 +61,26 @@ __all__ = ["quickrun"]
def get_model(folder: str, model_config: ModelConfig) -> Model: def get_model(folder: str, model_config: ModelConfig) -> Model:
"Return a model instance from a model config instance" """Return a model instance from a model config instance."""
path = model_config.model_file or os.path.join(folder, "model.py") path = model_config.model_file or os.path.join(folder, "model.py")
path = os.path.abspath(path) path = os.path.abspath(path)
if not os.path.isfile(path): if not os.path.isfile(path):
raise FileNotFoundError("Model file not found: '{path}'.") raise FileNotFoundError("Model file not found: '{path}'.")
if not path.endswith(".py"):
raise TypeError(f"Model file at '{path}' is not a '.py' file.")
with make_importable(os.path.dirname(path)): with make_importable(os.path.dirname(path)):
mod = importlib.import_module(os.path.basename(path)[:-3]) mod = importlib.import_module(os.path.basename(path)[:-3])
model = getattr(mod, model_config.model_name) model = getattr(mod, model_config.model_name)
if not isinstance(model, Model):
raise TypeError(
"Imported object from the model file is required to be a "
"'declearn.model.api.Model', but is a '{type(model)}'."
)
return model return model
def get_checkpoint(folder: str, expe_config: ExperimentConfig) -> str: def get_checkpoint(folder: str, expe_config: ExperimentConfig) -> str:
"""Return the checkpoint folder, either default or as given in config""" """Return the checkpoint folder, either default or as given in config."""
if expe_config.checkpoint: if expe_config.checkpoint:
checkpoint = expe_config.checkpoint checkpoint = expe_config.checkpoint
else: else:
...@@ -82,7 +89,7 @@ def get_checkpoint(folder: str, expe_config: ExperimentConfig) -> str: ...@@ -82,7 +89,7 @@ def get_checkpoint(folder: str, expe_config: ExperimentConfig) -> str:
return checkpoint return checkpoint
def run_server( async def run_server(
folder: str, folder: str,
network: NetworkServerConfig, network: NetworkServerConfig,
model_config: ModelConfig, model_config: ModelConfig,
...@@ -90,7 +97,7 @@ def run_server( ...@@ -90,7 +97,7 @@ def run_server(
config: FLRunConfig, config: FLRunConfig,
expe_config: ExperimentConfig, expe_config: ExperimentConfig,
) -> None: ) -> None:
"""Routine to run a FL server, called by `run_declearn_experiment`.""" """Routine to run a FL server, called by `quickrun`."""
# arguments serve modularity; pylint: disable=too-many-arguments # arguments serve modularity; pylint: disable=too-many-arguments
set_device_policy(gpu=False) set_device_policy(gpu=False)
model = get_model(folder, model_config) model = get_model(folder, model_config)
...@@ -100,24 +107,21 @@ def run_server( ...@@ -100,24 +107,21 @@ def run_server(
server = FederatedServer( server = FederatedServer(
model, network, optim, expe_config.metrics, checkpoint, logger model, network, optim, expe_config.metrics, checkpoint, logger
) )
server.run(config) await server.async_run(config)
def run_client( async def run_client(
folder: str, folder: str,
network: NetworkClientConfig, network: NetworkClientConfig,
model_config: ModelConfig,
expe_config: ExperimentConfig, expe_config: ExperimentConfig,
name: str, name: str,
paths: Dict[str, str], paths: Dict[str, str],
) -> None: ) -> None:
"""Routine to run a FL client, called by `run_declearn_experiment`.""" """Routine to run a FL client, called by `quickrun`."""
# arguments serve modularity; pylint: disable=too-many-arguments
# Overwrite client name based on folder name. # Overwrite client name based on folder name.
network.name = name network.name = name
# Make the model importable and disable GPU use. # Make the model importable and disable GPU use.
set_device_policy(gpu=False) set_device_policy(gpu=False)
_ = get_model(folder, model_config)
# Add checkpointer. # Add checkpointer.
checkpoint = get_checkpoint(folder, expe_config) checkpoint = get_checkpoint(folder, expe_config)
checkpoint = os.path.join(checkpoint, name) checkpoint = os.path.join(checkpoint, name)
...@@ -139,7 +143,7 @@ def run_client( ...@@ -139,7 +143,7 @@ def run_client(
client = FederatedClient( client = FederatedClient(
network, train, valid, checkpoint, logger=logger, verbose=False network, train, valid, checkpoint, logger=logger, verbose=False
) )
client.run() await client.async_run()
def get_toml_folder(config: str) -> Tuple[str, str]: def get_toml_folder(config: str) -> Tuple[str, str]:
...@@ -187,7 +191,7 @@ def locate_split_data(toml: str, folder: str) -> Dict: ...@@ -187,7 +191,7 @@ def locate_split_data(toml: str, folder: str) -> Dict:
def server_to_client_network( def server_to_client_network(
network_cfg: NetworkServerConfig, network_cfg: NetworkServerConfig,
) -> NetworkClientConfig: ) -> NetworkClientConfig:
"Convert server network config to client network config." """Convert server network config to client network config."""
return NetworkClientConfig.from_params( return NetworkClientConfig.from_params(
protocol=network_cfg.protocol, protocol=network_cfg.protocol,
server_uri=network_cfg.build_server().uri, server_uri=network_cfg.build_server().uri,
...@@ -195,8 +199,8 @@ def server_to_client_network( ...@@ -195,8 +199,8 @@ def server_to_client_network(
) )
def quickrun(config: str) -> None: async def quickrun(config: str) -> None:
"""Run a server and its clients using multiprocessing. """Run a server and its clients parallelly using asyncio.
The script requires to be provided with the path to a TOML file The script requires to be provided with the path to a TOML file
with all the elements required to configurate an FL experiment, with all the elements required to configurate an FL experiment,
...@@ -233,40 +237,39 @@ def quickrun(config: str) -> None: ...@@ -233,40 +237,39 @@ def quickrun(config: str) -> None:
- You may refer to a more detailed MNIST example on our GitLab repository. - You may refer to a more detailed MNIST example on our GitLab repository.
See the `examples/mnist_quickrun` folder. See the `examples/mnist_quickrun` folder.
""" """
# main script; pylint: disable=too-many-locals
toml, folder = get_toml_folder(config) toml, folder = get_toml_folder(config)
# locate split data or split it if needed # Locate split data or split it if needed.
client_dict = locate_split_data(toml, folder) client_dict = locate_split_data(toml, folder)
# Parse toml files # Parse toml files.
ntk_server_cfg = NetworkServerConfig.from_toml(toml, False, "network") ntk_server_cfg = NetworkServerConfig.from_toml(toml, False, "network")
ntk_client_cfg = server_to_client_network(ntk_server_cfg) ntk_client_cfg = server_to_client_network(ntk_server_cfg)
optim_cgf = FLOptimConfig.from_toml(toml, False, "optim") optim_cgf = FLOptimConfig.from_toml(toml, False, "optim")
run_cfg = FLRunConfig.from_toml(toml, False, "run") run_cfg = FLRunConfig.from_toml(toml, False, "run")
model_cfg = ModelConfig.from_toml(toml, False, "model", True) model_cfg = ModelConfig.from_toml(toml, False, "model", True)
expe_cfg = ExperimentConfig.from_toml(toml, False, "experiment", True) expe_cfg = ExperimentConfig.from_toml(toml, False, "experiment", True)
# Set up a (func, args) tuple specifying the server process. # Set up the server and client-wise coroutines.
p_server = ( coro_server = run_server(
run_server, folder, ntk_server_cfg, model_cfg, optim_cgf, run_cfg, expe_cfg
(folder, ntk_server_cfg, model_cfg, optim_cgf, run_cfg, expe_cfg),
)
# Set up the (func, args) tuples specifying client-wise processes.
p_client = []
for name, data_dict in client_dict.items():
client = (
run_client,
(folder, ntk_client_cfg, model_cfg, expe_cfg, name, data_dict),
)
p_client.append(client)
# Run each and every process in parallel.
success, outputs = run_as_processes(p_server, *p_client)
assert success, "The FL process failed:\n" + "\n".join(
str(exc) for exc in outputs if isinstance(exc, RuntimeError)
) )
coro_clients = [
run_client(folder, ntk_client_cfg, expe_cfg, name, data_dict)
for name, data_dict in client_dict.items()
]
# Run each and every coroutine in parallel.
await asyncio.gather(coro_server, *coro_clients)
def fire_quickrun(config) -> None:
"""Fire-wrappable caller to 'quickrun'."""
asyncio.run(quickrun(config))
fire_quickrun.__doc__ = quickrun.__doc__
def main() -> None: def main() -> None:
"""Fire-wrapped `quickrun`.""" """Fire-wrapped `quickrun`."""
fire.Fire(quickrun) fire.Fire(fire_quickrun)
if __name__ == "__main__": if __name__ == "__main__":
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This notebook is meant to be run in google colab. You can find import your local copy of the file in the the [colab welcome page](https://colab.research.google.com/). This notebook is meant to be run in google colab. You can find import your local copy of the file in the the [colab welcome page](https://colab.research.google.com/).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Setting up your declearn # Setting up your declearn
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We first clone the repo, to have both the package itself and the `examples` folder we will use in this tutorial, then naviguate to the package directory, and finally install the required dependencies We first clone the repo, to have both the package itself and the `examples` folder we will use in this tutorial, then naviguate to the package directory, and finally install the required dependencies
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# you may want to specify a release branch or tag # you may want to specify a release branch or tag
!git clone https://gitlab.inria.fr/magnet/declearn/declearn2 !git clone https://gitlab.inria.fr/magnet/declearn/declearn2
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
cd declearn2 cd declearn2
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Install the package, with TensorFlow and Websockets extra dependencies. # Install the package, with TensorFlow and Websockets extra dependencies.
# You may want to work in a dedicated virtual environment. # You may want to work in a dedicated virtual environment.
!pip install .[tensorflow,websockets] !pip install .[tensorflow,websockets]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Running your first experiment # Running your first experiment
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We are going to train a common model between three simulated clients on the classic [MNIST dataset](http://yann.lecun.com/exdb/mnist/). The input of the model is a set of images of handwritten digits, and the model needs to determine which number between 0 and 9 each image corresponds to. We are going to train a common model between three simulated clients on the classic [MNIST dataset](http://yann.lecun.com/exdb/mnist/). The input of the model is a set of images of handwritten digits, and the model needs to determine which number between 0 and 9 each image corresponds to.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## The model ## The model
To do this, we will use a simple CNN, defined in `examples/mnist_quickrun/model.py` To do this, we will use a simple CNN, defined in `examples/mnist_quickrun/model.py`
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from examples.mnist_quickrun.model import network from examples.mnist_quickrun.model import network
network.summary() network.summary()
``` ```
%% Output %% Output
Model: "sequential" Model: "sequential"
_________________________________________________________________ _________________________________________________________________
Layer (type) Output Shape Param # Layer (type) Output Shape Param #
================================================================= =================================================================
conv2d (Conv2D) (None, 26, 26, 8) 80 conv2d (Conv2D) (None, 26, 26, 8) 80
max_pooling2d (MaxPooling2D (None, 13, 13, 8) 0 max_pooling2d (MaxPooling2D (None, 13, 13, 8) 0
) )
dropout (Dropout) (None, 13, 13, 8) 0 dropout (Dropout) (None, 13, 13, 8) 0
flatten (Flatten) (None, 1352) 0 flatten (Flatten) (None, 1352) 0
dense (Dense) (None, 64) 86592 dense (Dense) (None, 64) 86592
dropout_1 (Dropout) (None, 64) 0 dropout_1 (Dropout) (None, 64) 0
dense_1 (Dense) (None, 10) 650 dense_1 (Dense) (None, 10) 650
================================================================= =================================================================
Total params: 87,322 Total params: 87,322
Trainable params: 87,322 Trainable params: 87,322
Non-trainable params: 0 Non-trainable params: 0
_________________________________________________________________ _________________________________________________________________
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## The data ## The data
We start by splitting the MNIST dataset between 3 clients and storing the output in the `examples/mnist_quickrun` folder. For this we use an experimental utility provided by `declearn`. We start by splitting the MNIST dataset between 3 clients and storing the output in the `examples/mnist_quickrun` folder. For this we use an experimental utility provided by `declearn`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from declearn.dataset import split_data from declearn.dataset import split_data
split_data(folder="examples/mnist_quickrun") split_data(folder="examples/mnist_quickrun")
``` ```
%% Output %% Output
Downloading MNIST source file train-images-idx3-ubyte.gz. Downloading MNIST source file train-images-idx3-ubyte.gz.
Downloading MNIST source file train-labels-idx1-ubyte.gz. Downloading MNIST source file train-labels-idx1-ubyte.gz.
Splitting data into 3 shards using the 'iid' scheme. Splitting data into 3 shards using the 'iid' scheme.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The python code above is equivalent to running `declearn-split examples/mnist_quickrun/` in a shell command-line. The python code above is equivalent to running `declearn-split examples/mnist_quickrun/` in a shell command-line.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Here is what the first image of the first client looks like: Here is what the first image of the first client looks like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
images = np.load("examples/mnist_quickrun/data_iid/client_0/train_data.npy") images = np.load("examples/mnist_quickrun/data_iid/client_0/train_data.npy")
sample_img = images[0] sample_img = images[0]
sample_fig = plt.imshow(sample_img,cmap='Greys') sample_fig = plt.imshow(sample_img,cmap='Greys')
``` ```
%% Output %% Output
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
For more information on how the `split_data` function works, you can look at the documentation. For more information on how the `split_data` function works, you can look at the documentation.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
print(split_data.__doc__) print(split_data.__doc__)
``` ```
%% Output %% Output
Randomly split a dataset into shards. Randomly split a dataset into shards.
The resulting folder structure is : The resulting folder structure is :
folder/ folder/
└─── data*/ └─── data*/
└─── client*/ └─── client*/
│ train_data.* - training data │ train_data.* - training data
│ train_target.* - training labels │ train_target.* - training labels
│ valid_data.* - validation data │ valid_data.* - validation data
│ valid_target.* - validation labels │ valid_target.* - validation labels
└─── client*/ └─── client*/
│ ... │ ...
Parameters Parameters
---------- ----------
folder: str, default = "." folder: str, default = "."
Path to the folder where to add a data folder Path to the folder where to add a data folder
holding output shard-wise files holding output shard-wise files
data_file: str or None, default=None data_file: str or None, default=None
Optional path to a folder where to find the data. Optional path to a folder where to find the data.
If None, default to the MNIST example. If None, default to the MNIST example.
target_file: str or int or None, default=None target_file: str or int or None, default=None
If str, path to the labels file to import, or name of a `data` If str, path to the labels file to import, or name of a `data`
column to use as labels (only if `data` points to a csv file). column to use as labels (only if `data` points to a csv file).
If int, index of a `data` column of to use as labels). If int, index of a `data` column of to use as labels).
Required if data is not None, ignored if data is None. Required if data is not None, ignored if data is None.
n_shards: int n_shards: int
Number of shards between which to split the data. Number of shards between which to split the data.
scheme: {"iid", "labels", "biased"}, default="iid" scheme: {"iid", "labels", "biased"}, default="iid"
Splitting scheme(s) to use. In all cases, shards contain mutually- Splitting scheme(s) to use. In all cases, shards contain mutually-
exclusive samples and cover the full raw training data. exclusive samples and cover the full raw training data.
- If "iid", split the dataset through iid random sampling. - If "iid", split the dataset through iid random sampling.
- If "labels", split into shards that hold all samples associated - If "labels", split into shards that hold all samples associated
with mutually-exclusive target classes. with mutually-exclusive target classes.
- If "biased", split the dataset through random sampling according - If "biased", split the dataset through random sampling according
to a shard-specific random labels distribution. to a shard-specific random labels distribution.
perc_train: float, default= 0.8 perc_train: float, default= 0.8
Train/validation split in each client dataset, must be in the Train/validation split in each client dataset, must be in the
]0,1] range. ]0,1] range.
seed: int or None, default=None seed: int or None, default=None
Optional seed to the RNG used for all sampling operations. Optional seed to the RNG used for all sampling operations.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Quickrun ## Quickrun
We can now run our experiment. As explained in the section 2.1 of the [quickstart documentation](https://magnet.gitlabpages.inria.fr/declearn/docs/latest/quickstart), using the `declearn-quickrun` entry-point requires a configuration file, some data, and a model: We can now run our experiment. As explained in the section 2.1 of the [quickstart documentation](https://magnet.gitlabpages.inria.fr/declearn/docs/latest/quickstart), using the `declearn-quickrun` entry-point requires a configuration file, some data, and a model:
* A TOML file, to store your experiment configurations. Here: * A TOML file, to store your experiment configurations. Here:
`examples/mnist_quickrun/config.toml`. `examples/mnist_quickrun/config.toml`.
* A folder with your data, split by client. Here: `examples/mnist_quickrun/data_iid` * A folder with your data, split by client. Here: `examples/mnist_quickrun/data_iid`
* A model python file, to declare your model wrapped in a `declearn` object. Here: `examples/mnist_quickrun/model.py`. * A model python file, to declare your model wrapped in a `declearn` object. Here: `examples/mnist_quickrun/model.py`.
We then only have to run the `quickrun` util with the path to the TOML file: We then only have to run the `quickrun` coroutine with the path to the TOML file:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from declearn.quickrun import quickrun from declearn.quickrun import quickrun
quickrun(config="examples/mnist_quickrun/config.toml") await quickrun(config="examples/mnist_quickrun/config.toml")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The python code above is equivalent to running `declearn-quickrun examples/mnist_quickrun/config.toml` in a shell command-line. The python code above is equivalent to running `declearn-quickrun examples/mnist_quickrun/config.toml` in a shell command-line.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The output obtained is the combination of the CLI output of our server and our clients, going through: The output obtained is the combination of the CLI output of our server and our clients, going through:
* `INFO:Server:Starting clients registration process.` : a first registration step, where clients register with the server * `INFO:Server:Starting clients registration process.` : a first registration step, where clients register with the server
* `INFO:Server:Sending initialization requests to clients.`: the initilization of the object needed for training on both the server and clients side. * `INFO:Server:Sending initialization requests to clients.`: the initilization of the object needed for training on both the server and clients side.
* `Server:INFO: Initiating training round 1`: the training starts, where each client makes its local update(s) and send the result to the server which aggregates them * `Server:INFO: Initiating training round 1`: the training starts, where each client makes its local update(s) and send the result to the server which aggregates them
* `INFO: Initiating evaluation round 1`: the model is evaluated at each round * `INFO: Initiating evaluation round 1`: the model is evaluated at each round
* `Server:INFO: Stopping training`: the training is finalized * `Server:INFO: Stopping training`: the training is finalized
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Results ## Results
You can have a look at the results in the `examples/mnist_quickrun/result_*` folder, including the metrics evolution during training. You can have a look at the results in the `examples/mnist_quickrun/result_*` folder, including the metrics evolution during training.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import pandas as pd import pandas as pd
import glob import glob
import os import os
res_file = glob.glob('examples/mnist_quickrun/result*') res_file = glob.glob('examples/mnist_quickrun/result*')
res = pd.read_csv(os.path.join(res_file[0],'server/metrics.csv')) res = pd.read_csv(os.path.join(res_file[0],'server/metrics.csv'))
res_fig = res.plot() res_fig = res.plot()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Experiment further # Experiment further
You can change the TOML config file to experiment with different strategies. You can change the TOML config file to experiment with different strategies.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
For instance, try splitting the data in a very heterogenous way, by distributing digits in mutually exclusive way between clients. For instance, try splitting the data in a very heterogenous way, by distributing digits in mutually exclusive way between clients.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
split_data(folder="examples/mnist_quickrun",scheme='labels') split_data(folder="examples/mnist_quickrun",scheme='labels')
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And change the `examples/mnist_quickrun/config.toml` file with: And change the `examples/mnist_quickrun/config.toml` file with:
``` ```
[data] [data]
data_folder = "examples/mnist_quickrun/data_labels" data_folder = "examples/mnist_quickrun/data_labels"
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you run the model as is, you should see a drop of performance If you run the model as is, you should see a drop of performance
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
quickrun(config="examples/mnist_quickrun/config.toml") quickrun(config="examples/mnist_quickrun/config.toml")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Now try modifying the `examples/mnist_quickrun/config.toml` file like this, to implement the [scaffold algorithm](https://arxiv.org/abs/1910.06378) and running the experiment again. Now try modifying the `examples/mnist_quickrun/config.toml` file like this, to implement the [scaffold algorithm](https://arxiv.org/abs/1910.06378) and running the experiment again.
``` ```
[optim] [optim]
[optim.client_opt] [optim.client_opt]
lrate = 0.005 lrate = 0.005
modules = ["scaffold-client"] modules = ["scaffold-client"]
[optim.server_opt] [optim.server_opt]
lrate = 1.0 lrate = 1.0
modules = ["scaffold-client"] modules = ["scaffold-client"]
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
quickrun(config="examples/mnist_quickrun/config.toml") quickrun(config="examples/mnist_quickrun/config.toml")
``` ```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment