Repository: awslabs/gluonts

Issue:
multiprocessing: AssertionError: Something went wrong with the worker initialization
## Description
I tried to use the Evaluator in from "gluonts.evaluation import Evaluator", but an assertion error occurred related to multiprocessing. When I re-downloaded the tutorial in the document (https://gluon-ts.mxnet.io/examples/basic_forecasting_tutorial/tutorial.html#Create-your-own-forecast-model), the same thing happened. The code worked several weeks ago with the same OS. Besides, I changed the multiprocessing version back to the previous version, the bug still existed.

## To Reproduce

```python
from gluonts.evaluation import Evaluator
evaluator = Evaluator(quantiles=[0.1, 0.5, 0.9])
agg_metrics, item_metrics = evaluator(iter(tss), iter(forecasts), num_series=len(dataset.test))
```
## Error message or code output
(Paste the complete error message, including stack trace, or the undesired output that the above snippet produces.)

```
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/pool.py", line 125, in worker
    result = (True, func(*args, **kwds))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/pool.py", line 48, in mapstar
    return list(map(*args))
  File "/Users/caixiyan/Library/Python/3.8/lib/python/site-packages/gluonts/evaluation/_base.py", line 808, in _worker_fun
    assert isinstance(
AssertionError: Something went wrong with the worker initialization.

AssertionError                            Traceback (most recent call last)
<ipython-input-29-b25f87fe7d03> in <module>
      1 evaluator = Evaluator(quantiles=[0.1, 0.5, 0.9])
----> 2 agg_metrics, item_metrics = evaluator(iter(tss), iter(forecasts), num_series=len(dataset.test))

~/Library/Python/3.8/lib/python/site-packages/gluonts/evaluation/_base.py in __call__(self, ts_iterator, fcst_iterator, num_series)
    168                     initializer=_worker_init(self), processes=self.num_workers
    169                 )
--> 170                 rows = mp_pool.map(
    171                     func=_worker_fun,
    172                     iterable=iter(it),

/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/pool.py in map(self, func, iterable, chunksize)
    362         in a list that is returned.
    363         '''
--> 364         return self._map_async(func, iterable, mapstar, chunksize).get()
    365 
    366     def starmap(self, func, iterable, chunksize=None):

/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/pool.py in get(self, timeout)
    766             return self._value
    767         else:
--> 768             raise self._value
    769 
    770     def _set(self, i, obj):

AssertionError: Something went wrong with the worker initialization.

```
## Environment
- Operating system: MacOS 10.15.6 
- Python version: 3.8
- GluonTS version: 0.5.1
- MXNet version: 1.6.0
- multiprocessing version: 0.70.10 (also used 0.70.9)
(Add as much information about your environment as possible, e.g. dependencies versions.)



Discussion:
@arianacai1997 could you try on python 3.7?
Thanks for the advice. It works!

let's leave this open then so that we can track python 3.8 support
@arianacai1997 Hey bro, What version of Python are you working on? I found the same BUG in Python3.7.4

Resolving patch:
diff --git a/src/gluonts/core/serde/_parse.py b/src/gluonts/core/serde/_parse.py
--- a/src/gluonts/core/serde/_parse.py
+++ b/src/gluonts/core/serde/_parse.py
@@ -72,6 +72,11 @@ def parse_name_constant(v: ast.NameConstant):
     return v.value
 
 
+@parse_expr.register(ast.Constant)
+def parse_constant(v: ast.Constant):
+    return v.value
+
+
 @parse_expr.register(ast.Attribute)
 def parse_attribute(v: ast.Attribute, path=()):
     if isinstance(v.value, ast.Name):
diff --git a/src/gluonts/evaluation/_base.py b/src/gluonts/evaluation/_base.py
--- a/src/gluonts/evaluation/_base.py
+++ b/src/gluonts/evaluation/_base.py
@@ -14,6 +14,7 @@
 import logging
 import multiprocessing
 import sys
+from functools import partial
 from itertools import chain, tee
 from typing import (
     Any,
@@ -39,6 +40,11 @@ def nan_if_masked(a: Union[float, np.ma.core.MaskedConstant]) -> float:
     return a if a is not np.ma.masked else np.nan
 
 
+def worker_function(evaluator: "Evaluator", inp: tuple):
+    ts, forecast = inp
+    return evaluator.get_metrics_per_ts(ts, forecast)
+
+
 class Evaluator:
     """
     Evaluator class, to compute accuracy metrics by comparing observations
@@ -142,10 +148,10 @@ def __call__(
         ) as it, np.errstate(invalid="ignore"):
             if self.num_workers and not sys.platform == "win32":
                 mp_pool = multiprocessing.Pool(
-                    initializer=_worker_init(self), processes=self.num_workers
+                    initializer=None, processes=self.num_workers
                 )
                 rows = mp_pool.map(
-                    func=_worker_fun,
+                    func=partial(worker_function, self),
                     iterable=iter(it),
                     chunksize=self.chunk_size,
                 )
@@ -815,21 +821,3 @@ def __call__(
                     all_agg_metrics[prefix + metric] = value
 
         return all_agg_metrics, all_metrics_per_ts
-
-
-# This is required for the multiprocessing to work.
-_worker_evaluator: Optional[Evaluator] = None
-
-
-def _worker_init(evaluator: Evaluator):
-    global _worker_evaluator
-    _worker_evaluator = evaluator
-
-
-def _worker_fun(inp: tuple):
-    ts, forecast = inp
-    global _worker_evaluator
-    assert isinstance(
-        _worker_evaluator, Evaluator
-    ), "Something went wrong with the worker initialization."
-    return _worker_evaluator.get_metrics_per_ts(ts, forecast)


Test patch:
diff --git a/test/dataset/test_multiprocessing_loader.py b/test/dataset/test_multiprocessing_loader.py
--- a/test/dataset/test_multiprocessing_loader.py
+++ b/test/dataset/test_multiprocessing_loader.py
@@ -144,6 +144,14 @@ def get_transformation_counts(dataset):
     return transformation_counts
 
 
+class ExactlyOneSampler(InstanceSampler):
+    def __call__(self, ts: np.ndarray) -> np.ndarray:
+        a, b = self._get_bounds(ts)
+        window_size = b - a + 1
+        assert window_size > 0
+        return np.array([a])
+
+
 # The idea is to test that the validation data loader yields equivalent results
 def test_validation_loader_equivalence() -> None:
     (
@@ -225,13 +233,6 @@ def test_train_loader_goes_over_all_data(num_workers) -> None:
     ]
 
     def test_dataset(dataset):
-        class ExactlyOneSampler(InstanceSampler):
-            def __call__(self, ts: np.ndarray) -> np.ndarray:
-                a, b = self._get_bounds(ts)
-                window_size = b - a + 1
-                assert window_size > 0
-                return np.array([a])
-
         transformation = InstanceSplitter(
             target_field=FieldName.TARGET,
             is_pad_field=FieldName.IS_PAD,