Repository: alibaba/FederatedScope

Issue:
Inappropriate way of assigning values to Client data in toy and vfl synthetic dataset
When applying feature transformation in `ClientData`, we must use `deepcopy`. Otherwise, the data in server and client will be inconsistent (in server, x = f(x), but in client, x=f(f(x))).
And I've fixed this in https://github.com/alibaba/FederatedScope/pull/486/commits/ae896cd5740dc0b34f03bc637a724edf3f3be77e, please help me to double-check if there are the same issues on other datasets, thanks! @xieyxclack 


Discussion:
There is a minor bug in `vfl_synthetic_data`, where the `config.vertical.dims` is inconsistent with those in `xgb` module.
https://github.com/alibaba/FederatedScope/blob/f4872600f01ea9b4dba9fb4c95f7145f4dc419eb/federatedscope/vertical_fl/dataloader/dataloader.py#L50

I've fix it in [`74bceeb`](https://github.com/alibaba/FederatedScope/pull/486/commits/74bceeb62433cd8591022ea0945a4a7434ad9f44):
```python
total_dims = config.vertical.dims[-1]
```
Thank you very much for pointing out and fixing this issue! 

Resolving patch:
diff --git a/federatedscope/core/parallel/parallel_runner.py b/federatedscope/core/parallel/parallel_runner.py
--- a/federatedscope/core/parallel/parallel_runner.py
+++ b/federatedscope/core/parallel/parallel_runner.py
@@ -195,6 +195,7 @@ def __init__(self, rank, config, server_class, receive_channel,
         self.server_id = 0
         self.resource_info = resource_info
         self.client_resource_info = client_resource_info
+        self.serial_num_for_msg = 0
 
     def setup(self):
         self.config.defrost()
@@ -248,6 +249,8 @@ def run(self):
                 # For the server, move the received message to a
                 # cache for reordering the messages according to
                 # the timestamps
+                msg.serial_num = self.serial_num_for_msg
+                self.serial_num_for_msg += 1
                 heapq.heappush(server_msg_cache, msg)
             elif len(server_msg_cache) > 0:
                 msg = heapq.heappop(server_msg_cache)
diff --git a/federatedscope/core/workers/server.py b/federatedscope/core/workers/server.py
--- a/federatedscope/core/workers/server.py
+++ b/federatedscope/core/workers/server.py
@@ -132,10 +132,10 @@ def __init__(self,
 
         if self._cfg.federate.make_global_eval:
             # set up a trainer for conducting evaluation in server
-            assert self.model is not None
+            assert self.models is not None
             assert self.data is not None
             self.trainer = get_trainer(
-                model=self.model,
+                model=self.models[0],
                 data=self.data,
                 device=self.device,
                 config=self._cfg,
@@ -456,7 +456,7 @@ def _perform_federated_aggregation(self):
                 staleness.append((client_id, self.state - state))
 
             # Trigger the monitor here (for training)
-            self._monitor.calc_model_metric(self.model.state_dict(),
+            self._monitor.calc_model_metric(self.models[0].state_dict(),
                                             msg_list,
                                             rnd=self.state)
 
@@ -664,7 +664,7 @@ def broadcast_model_para(self,
             model_para = [{} if skip_broadcast else model.state_dict()
                           for model in self.models]
         else:
-            model_para = {} if skip_broadcast else self.model.state_dict()
+            model_para = {} if skip_broadcast else self.models[0].state_dict()
 
         # We define the evaluation happens at the end of an epoch
         rnd = self.state - 1 if msg_type == 'evaluate' else self.state
@@ -781,7 +781,7 @@ def trigger_for_start(self):
             else:
                 if self._cfg.backend == 'torch':
                     model_size = sys.getsizeof(pickle.dumps(
-                        self.model)) / 1024.0 * 8.
+                        self.models[0])) / 1024.0 * 8.
                 else:
                     # TODO: calculate model size for TF Model
                     model_size = 1.0
@@ -851,7 +851,7 @@ def terminate(self, msg_type='finish'):
         if self.model_num > 1:
             model_para = [model.state_dict() for model in self.models]
         else:
-            model_para = self.model.state_dict()
+            model_para = self.models[0].state_dict()
 
         self._monitor.finish_fl()
 
diff --git a/federatedscope/tabular/dataloader/toy.py b/federatedscope/tabular/dataloader/toy.py
--- a/federatedscope/tabular/dataloader/toy.py
+++ b/federatedscope/tabular/dataloader/toy.py
@@ -1,3 +1,4 @@
+import copy
 import pickle
 
 import numpy as np
@@ -58,7 +59,7 @@ def _generate_data(client_num=5,
         test_y = np.expand_dims(test_y, -1)
         test_data = {'x': test_x, 'y': test_y}
         for each_client in range(1, client_num + 1):
-            data[each_client]['test'] = test_data
+            data[each_client]['test'] = copy.deepcopy(test_data)
 
         # val data
         val_x = np.random.normal(loc=0.0,
@@ -68,7 +69,7 @@ def _generate_data(client_num=5,
         val_y = np.expand_dims(val_y, -1)
         val_data = {'x': val_x, 'y': val_y}
         for each_client in range(1, client_num + 1):
-            data[each_client]['val'] = val_data
+            data[each_client]['val'] = copy.deepcopy(val_data)
 
         # server_data
         data[0] = dict()


Test patch:
diff --git a/.github/workflows/test_atc.yml b/.github/workflows/test_atc.yml
--- a/.github/workflows/test_atc.yml
+++ b/.github/workflows/test_atc.yml
@@ -7,7 +7,7 @@ on:
 
 jobs:
   run:
-    if: false == contains(github.event.pull_request.title, 'WIP')
+    if: (false == contains(github.event.pull_request.title, 'WIP') && github.repository == 'alibaba/FederatedScope')
     runs-on: ${{ matrix.os }}
     timeout-minutes: 30
     strategy: