Repository: cupy/cupy

Issue:
Error with _ufunc_config.config
When dispatching using CuPy from Dask (multithreaded python) there is a failure due to `_ufunc_config.config`
* Conditions (you can just paste the output of `python -c 'import cupy; cupy.show_config()'`)

```
CuPy Version          : 7.0.0
CUDA Root             : /usr/local/cuda-10.1/
CUDA Build Version    : 10010
CUDA Driver Version   : 10010
CUDA Runtime Version  : 10010
cuBLAS Version        : 10201
cuFFT Version         : 10101
cuRAND Version        : 10101
cuSOLVER Version      : (10, 2, 0)
cuSPARSE Version      : 10300
NVRTC Version         : (10, 1)
cuDNN Build Version   : None
cuDNN Version         : None
NCCL Build Version    : None
NCCL Runtime Version  : None
```
* Code to reproduce
```
import dask, cupy
import dask.array as da

rs = dask.array.random.RandomState(RandomState=cupy.random.RandomState)
x = rs.random((100, 100))
x = x.persist()
u, s, v = da.linalg.svd(x)
u.compute()
```
* Error messages, stack traces, or logs
<details>
<summary>Traceback</summary>

```
Traceback (most recent call last):
  File "cpu-svd.py", line 10, in <module>
    u.compute()
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/base.py", line 165, in compute
    (result,) = compute(self, traverse=False, **kwargs)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/base.py", line 436, in compute
    results = schedule(dsk, keys, **kwargs)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/threaded.py", line 81, in get
    **kwargs
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/local.py", line 486, in get_async
    raise_exception(exc, tb)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/local.py", line 316, in reraise
    raise exc
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/local.py", line 222, in execute_task
    result = _execute_task(task, data)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/core.py", line 119, in _execute_task
    return func(*args2)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/optimization.py", line 982, in __call__
    return core.get(self.dsk, self.outkey, dict(zip(self.inkeys, args)))
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/core.py", line 149, in get
    result = _execute_task(task, cache)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/core.py", line 119, in _execute_task
    return func(*args2)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/dask/array/linalg.py", line 48, in _wrapped_qr
    return np.linalg.qr(a)
  File "<__array_function__ internals>", line 6, in qr
  File "cupy/core/core.pyx", line 1344, in cupy.core.core.ndarray.__array_function__
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/cupy/linalg/decomposition.py", line 248, in qr
    geqrf, dev_info)
  File "/datasets/bzaitlen/miniconda3/envs/rapids-12/lib/python3.7/site-packages/cupy/linalg/util.py", line 42, in _check_cusolver_dev_info_if_synchronization_allowed
    print(cupyx._ufunc_config.config.linalg)
AttributeError: '_thread._local' object has no attribute 'linalg'
```
</details>


Rather than using `threading.local()` here 
https://github.com/cupy/cupy/blob/f06f509167957240d17924965238cb0901b700ab/cupyx/_ufunc_config.py#L4-L13

We could use `types` for a two line code change:

```python
import types

config = types.SimpleNamespace()
```

But I may be missing the reason for using threads here


Discussion:

Resolving patch:
diff --git a/cupy/linalg/util.py b/cupy/linalg/util.py
--- a/cupy/linalg/util.py
+++ b/cupy/linalg/util.py
@@ -33,8 +33,7 @@ def _check_cusolver_dev_info_if_synchronization_allowed(routine, dev_info):
     # documentation.
     assert isinstance(dev_info, core.ndarray)
     assert dev_info.size == 1
-
-    config_linalg = cupyx._ufunc_config.config.linalg
+    config_linalg = cupyx._ufunc_config.get_config_linalg()
     # Only 'ignore' and 'raise' are currently supported.
     if config_linalg == 'ignore':
         return
@@ -55,7 +54,7 @@ def _check_cublas_info_array_if_synchronization_allowed(routine, info_array):
     assert isinstance(info_array, core.ndarray)
     assert info_array.ndim == 1
 
-    config_linalg = cupyx._ufunc_config.config.linalg
+    config_linalg = cupyx._ufunc_config.get_config_linalg()
     # Only 'ignore' and 'raise' are currently supported.
     if config_linalg == 'ignore':
         return
diff --git a/cupyx/_ufunc_config.py b/cupyx/_ufunc_config.py
--- a/cupyx/_ufunc_config.py
+++ b/cupyx/_ufunc_config.py
@@ -1,16 +1,51 @@
 import contextlib
 import threading
 
-config = threading.local()
-config.divide = None
-config.over = None
-config.under = None
-config.invalid = None
-# In favor of performance, the `devInfo` input/output from cuSOLVER routine
-# calls that is necessary to check the validity of the other outputs, are
-# ignored, as D2H copy incurring device synchronizations would otherwise be
-# required.
-config.linalg = 'ignore'
+_config = threading.local()
+
+
+def get_config_divide():
+    try:
+        value = _config.divide
+    except AttributeError:
+        value = _config.divide = None
+    return value
+
+
+def get_config_over():
+    try:
+        value = _config.over
+    except AttributeError:
+        value = _config.over = None
+    return value
+
+
+def get_config_under():
+    try:
+        value = _config.under
+    except AttributeError:
+        value = _config.under = None
+    return value
+
+
+def get_config_invalid():
+    try:
+        value = _config.invalid
+    except AttributeError:
+        value = _config.invalid = None
+    return value
+
+
+def get_config_linalg():
+    # In favor of performance, the `devInfo` input/output from cuSOLVER routine
+    # calls that is necessary to check the validity of the other outputs, are
+    # ignored, as D2H copy incurring device synchronizations would otherwise be
+    # required.
+    try:
+        value = _config.linalg
+    except AttributeError:
+        value = _config.linalg = 'ignore'
+    return value
 
 
 @contextlib.contextmanager
@@ -44,11 +79,11 @@ def seterr(*, divide=None, over=None, under=None, invalid=None, linalg=None):
 
     old_state = geterr()
 
-    config.divide = divide
-    config.under = under
-    config.over = over
-    config.invalid = invalid
-    config.linalg = linalg
+    _config.divide = divide
+    _config.under = under
+    _config.over = over
+    _config.invalid = invalid
+    _config.linalg = linalg
 
     return old_state
 
@@ -58,9 +93,9 @@ def geterr():
     TODO(hvy): Write docs.
     """
     return dict(
-        divide=config.divide,
-        over=config.over,
-        under=config.under,
-        invalid=config.invalid,
-        linalg=config.linalg,
+        divide=get_config_divide(),
+        over=get_config_over(),
+        under=get_config_under(),
+        invalid=get_config_invalid(),
+        linalg=get_config_linalg(),
     )


Test patch: