Skip to content

Commit 4db3879

Browse files
authored
Import mxnet and tensorflow only if explicitly enabled (#890)
* Import mxnet and tensorflow only if explicitly enabled * Ignore import errors for mxnet/tensorflow in tests * Add enable_{mxnet,tensorflow} to thinc.api and docs * Update intro example notebook * Add warnings/info to docs * Add deprecation warnings to enable_ methods * Extend error messages in assert_{mxnet,tensorflow}_installed
1 parent 84c109e commit 4db3879

14 files changed

+116
-35
lines changed

.github/workflows/tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ jobs:
136136
shell: bash --noprofile --norc -o pipefail {0}
137137

138138
- name: Run tests with extras
139-
run: python -m pytest --pyargs thinc --cov=thinc --cov-report=term
139+
run: python -m pytest --pyargs thinc --cov=thinc --cov-report=term -p thinc.tests.enable_tensorflow -p thinc.tests.enable_mxnet
140140

141141
- name: Run tests for thinc-apple-ops
142142
run: |

examples/00_intro_to_thinc.ipynb

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"outputs": [],
2525
"source": [
26-
"!pip install \"thinc>=8.0.0\" \"ml_datasets>=0.2.0\" \"tqdm>=4.41\""
26+
"!pip install \"thinc>=8.2.0\" \"ml_datasets>=0.2.0\" \"tqdm>=4.41\""
2727
]
2828
},
2929
{
@@ -1050,7 +1050,8 @@
10501050
"source": [
10511051
"from tensorflow.keras.layers import Dense, Dropout\n",
10521052
"from tensorflow.keras.models import Sequential\n",
1053-
"from thinc.api import TensorFlowWrapper, Adam\n",
1053+
"from thinc.api import enable_tensorflow, TensorFlowWrapper, Adam\n",
1054+
"enable_tensorflow()\n",
10541055
"\n",
10551056
"width = 32\n",
10561057
"nO = 10\n",
@@ -1373,8 +1374,9 @@
13731374
"outputs": [],
13741375
"source": [
13751376
"from mxnet.gluon.nn import Dense, Sequential, Dropout\n",
1376-
"from thinc.api import MXNetWrapper, chain, Softmax\n",
1377+
"from thinc.api import enable_mxnet, MXNetWrapper, chain, Softmax\n",
13771378
"import thinc.util\n",
1379+
"enable_mxnet()\n",
13781380
"\n",
13791381
"assert thinc.util.has_mxnet\n",
13801382
"\n",

thinc/api.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use_pytorch_for_gpu_memory,
1212
use_tensorflow_for_gpu_memory,
1313
)
14-
from .compat import has_cupy
14+
from .compat import enable_mxnet, enable_tensorflow, has_cupy
1515
from .config import Config, ConfigValidationError, registry
1616
from .initializers import (
1717
configure_normal_init,
@@ -190,6 +190,8 @@
190190
"torch2xp", "xp2torch", "tensorflow2xp", "xp2tensorflow", "mxnet2xp", "xp2mxnet",
191191
"get_torch_default_device",
192192
# .compat
193+
"enable_mxnet",
194+
"enable_tensorflow",
193195
"has_cupy",
194196
# .backends
195197
"get_ops", "set_current_ops", "get_current_ops", "use_ops",

thinc/backends/_cupy_allocators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def cupy_tensorflow_allocator(size_in_bytes: int):
1212
sitting in the other library's pool.
1313
"""
1414
size_in_bytes = max(1024, size_in_bytes)
15-
tensor = tensorflow.zeros((size_in_bytes // 4,), dtype=tensorflow.dtypes.float32)
15+
tensor = tensorflow.zeros((size_in_bytes // 4,), dtype=tensorflow.dtypes.float32) # type: ignore
1616
# We convert to cupy via dlpack, so that we can get a memory pointer.
1717
cupy_array = cast(ArrayXd, tensorflow2xp(tensor))
1818
address = int(cupy_array.data)

thinc/compat.py

+31-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
from packaging.version import Version
24

35
try: # pragma: no cover
@@ -50,25 +52,45 @@
5052
has_torch_amp = False
5153
torch_version = Version("0.0.0")
5254

53-
try: # pragma: no cover
55+
56+
def enable_tensorflow():
57+
warn_msg = (
58+
"Built-in TensorFlow support will be removed in Thinc v9. If you need "
59+
"TensorFlow support in the future, you can transition to using a "
60+
"custom copy of the current TensorFlowWrapper in your package or "
61+
"project."
62+
)
63+
warnings.warn(warn_msg, DeprecationWarning)
64+
global tensorflow, has_tensorflow, has_tensorflow_gpu
5465
import tensorflow
5566
import tensorflow.experimental.dlpack
5667

5768
has_tensorflow = True
5869
has_tensorflow_gpu = len(tensorflow.config.get_visible_devices("GPU")) > 0
59-
except ImportError: # pragma: no cover
60-
tensorflow = None
61-
has_tensorflow = False
62-
has_tensorflow_gpu = False
6370

6471

65-
try: # pragma: no cover
72+
tensorflow = None
73+
has_tensorflow = False
74+
has_tensorflow_gpu = False
75+
76+
77+
def enable_mxnet():
78+
warn_msg = (
79+
"Built-in MXNet support will be removed in Thinc v9. If you need "
80+
"MXNet support in the future, you can transition to using a "
81+
"custom copy of the current MXNetWrapper in your package or "
82+
"project."
83+
)
84+
warnings.warn(warn_msg, DeprecationWarning)
85+
global mxnet, has_mxnet
6686
import mxnet
6787

6888
has_mxnet = True
69-
except ImportError: # pragma: no cover
70-
mxnet = None
71-
has_mxnet = False
89+
90+
91+
mxnet = None
92+
has_mxnet = False
93+
7294

7395
try:
7496
import h5py

thinc/layers/tensorflowwrapper.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# mypy: ignore-errors
12
from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar
23

34
import srsly

thinc/shims/mxnet.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# mypy: ignore-errors
12
import copy
23
from typing import Any, cast
34

thinc/shims/tensorflow.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# mypy: ignore-errors
12
import contextlib
23
import copy
34
from io import BytesIO

thinc/tests/enable_mxnet.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from thinc.compat import enable_mxnet
2+
3+
try:
4+
enable_mxnet()
5+
except ImportError:
6+
pass

thinc/tests/enable_tensorflow.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from thinc.compat import enable_tensorflow
2+
3+
try:
4+
enable_tensorflow()
5+
except ImportError:
6+
pass

thinc/util.py

+21-19
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def is_torch_mps_array(obj: Any) -> bool: # pragma: no cover
151151
def is_tensorflow_array(obj: Any) -> bool: # pragma: no cover
152152
if not has_tensorflow:
153153
return False
154-
elif isinstance(obj, tf.Tensor):
154+
elif isinstance(obj, tf.Tensor): # type: ignore
155155
return True
156156
else:
157157
return False
@@ -164,7 +164,7 @@ def is_tensorflow_gpu_array(obj: Any) -> bool: # pragma: no cover
164164
def is_mxnet_array(obj: Any) -> bool: # pragma: no cover
165165
if not has_mxnet:
166166
return False
167-
elif isinstance(obj, mx.nd.NDArray):
167+
elif isinstance(obj, mx.nd.NDArray): # type: ignore
168168
return True
169169
else:
170170
return False
@@ -316,15 +316,17 @@ def get_width(
316316

317317
def assert_tensorflow_installed() -> None: # pragma: no cover
318318
"""Raise an ImportError if TensorFlow is not installed."""
319-
template = "TensorFlow support requires {pkg}: pip install thinc[tensorflow]"
319+
template = "TensorFlow support requires {pkg}: pip install thinc[tensorflow]\n\nEnable TensorFlow support with thinc.api.enable_tensorflow()"
320320
if not has_tensorflow:
321-
raise ImportError(template.format(pkg="tensorflow>=2.0.0"))
321+
raise ImportError(template.format(pkg="tensorflow>=2.0.0,<2.6.0"))
322322

323323

324324
def assert_mxnet_installed() -> None: # pragma: no cover
325325
"""Raise an ImportError if MXNet is not installed."""
326326
if not has_mxnet:
327-
raise ImportError("MXNet support requires mxnet: pip install thinc[mxnet]")
327+
raise ImportError(
328+
"MXNet support requires mxnet: pip install thinc[mxnet]\n\nEnable MXNet support with thinc.api.enable_mxnet()"
329+
)
328330

329331

330332
def assert_pytorch_installed() -> None: # pragma: no cover
@@ -429,32 +431,32 @@ def torch2xp(
429431

430432
def xp2tensorflow(
431433
xp_tensor: ArrayXd, requires_grad: bool = False, as_variable: bool = False
432-
) -> "tf.Tensor": # pragma: no cover
434+
) -> "tf.Tensor": # type: ignore # pragma: no cover
433435
"""Convert a numpy or cupy tensor to a TensorFlow Tensor or Variable"""
434436
assert_tensorflow_installed()
435437
if hasattr(xp_tensor, "toDlpack"):
436438
dlpack_tensor = xp_tensor.toDlpack() # type: ignore
437-
tf_tensor = tf.experimental.dlpack.from_dlpack(dlpack_tensor)
439+
tf_tensor = tf.experimental.dlpack.from_dlpack(dlpack_tensor) # type: ignore
438440
elif hasattr(xp_tensor, "__dlpack__"):
439441
dlpack_tensor = xp_tensor.__dlpack__() # type: ignore
440-
tf_tensor = tf.experimental.dlpack.from_dlpack(dlpack_tensor)
442+
tf_tensor = tf.experimental.dlpack.from_dlpack(dlpack_tensor) # type: ignore
441443
else:
442-
tf_tensor = tf.convert_to_tensor(xp_tensor)
444+
tf_tensor = tf.convert_to_tensor(xp_tensor) # type: ignore
443445
if as_variable:
444446
# tf.Variable() automatically puts in GPU if available.
445447
# So we need to control it using the context manager
446-
with tf.device(tf_tensor.device):
447-
tf_tensor = tf.Variable(tf_tensor, trainable=requires_grad)
448+
with tf.device(tf_tensor.device): # type: ignore
449+
tf_tensor = tf.Variable(tf_tensor, trainable=requires_grad) # type: ignore
448450
if requires_grad is False and as_variable is False:
449451
# tf.stop_gradient() automatically puts in GPU if available.
450452
# So we need to control it using the context manager
451-
with tf.device(tf_tensor.device):
452-
tf_tensor = tf.stop_gradient(tf_tensor)
453+
with tf.device(tf_tensor.device): # type: ignore
454+
tf_tensor = tf.stop_gradient(tf_tensor) # type: ignore
453455
return tf_tensor
454456

455457

456458
def tensorflow2xp(
457-
tf_tensor: "tf.Tensor", *, ops: Optional["Ops"] = None
459+
tf_tensor: "tf.Tensor", *, ops: Optional["Ops"] = None # type: ignore
458460
) -> ArrayXd: # pragma: no cover
459461
"""Convert a Tensorflow tensor to numpy or cupy tensor depending on the `ops` parameter.
460462
If `ops` is `None`, the type of the resultant tensor will be determined by the source tensor's device.
@@ -466,7 +468,7 @@ def tensorflow2xp(
466468
if isinstance(ops, NumpyOps):
467469
return tf_tensor.numpy()
468470
else:
469-
dlpack_tensor = tf.experimental.dlpack.to_dlpack(tf_tensor)
471+
dlpack_tensor = tf.experimental.dlpack.to_dlpack(tf_tensor) # type: ignore
470472
return cupy_from_dlpack(dlpack_tensor)
471473
else:
472474
if isinstance(ops, NumpyOps) or ops is None:
@@ -477,21 +479,21 @@ def tensorflow2xp(
477479

478480
def xp2mxnet(
479481
xp_tensor: ArrayXd, requires_grad: bool = False
480-
) -> "mx.nd.NDArray": # pragma: no cover
482+
) -> "mx.nd.NDArray": # type: ignore # pragma: no cover
481483
"""Convert a numpy or cupy tensor to a MXNet tensor."""
482484
assert_mxnet_installed()
483485
if hasattr(xp_tensor, "toDlpack"):
484486
dlpack_tensor = xp_tensor.toDlpack() # type: ignore
485-
mx_tensor = mx.nd.from_dlpack(dlpack_tensor)
487+
mx_tensor = mx.nd.from_dlpack(dlpack_tensor) # type: ignore
486488
else:
487-
mx_tensor = mx.nd.from_numpy(xp_tensor)
489+
mx_tensor = mx.nd.from_numpy(xp_tensor) # type: ignore
488490
if requires_grad:
489491
mx_tensor.attach_grad()
490492
return mx_tensor
491493

492494

493495
def mxnet2xp(
494-
mx_tensor: "mx.nd.NDArray", *, ops: Optional["Ops"] = None
496+
mx_tensor: "mx.nd.NDArray", *, ops: Optional["Ops"] = None # type: ignore
495497
) -> ArrayXd: # pragma: no cover
496498
"""Convert a MXNet tensor to a numpy or cupy tensor."""
497499
from .api import NumpyOps

website/docs/api-layers.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ model, e.g. `chain(f, g)` computes `g(f(x))`.
10031003

10041004
| Argument | Type | Description |
10051005
| ----------- | -------------- | --------------------------------- |
1006-
| `layer1 ` | <tt>Model</tt> | The first model to compose. |
1006+
| `layer1` | <tt>Model</tt> | The first model to compose. |
10071007
| `layer2` | <tt>Model</tt> | The second model to compose. |
10081008
| `*layers` | <tt>Model</tt> | Any additional models to compose. |
10091009
| **RETURNS** | <tt>Model</tt> | The composed feed-forward model. |
@@ -1795,6 +1795,16 @@ https://github.com/explosion/thinc/blob/master/thinc/layers/torchscriptwrapper.p
17951795

17961796
</inline-list>
17971797

1798+
<infobox variant="warning">
1799+
In Thinc v8.2+, TensorFlow support is not enabled by default. To enable TensorFlow:
1800+
1801+
```python
1802+
from thinc.api import enable_tensorflow
1803+
enable_tensorflow()
1804+
```
1805+
1806+
</infobox>
1807+
17981808
Wrap a [TensorFlow](https://tensorflow.org) model, so that it has the same API
17991809
as Thinc models. To optimize the model, you'll need to create a TensorFlow
18001810
optimizer and call `optimizer.apply_gradients` after each batch. To allow
@@ -1820,6 +1830,16 @@ https://github.com/explosion/thinc/blob/master/thinc/layers/tensorflowwrapper.py
18201830

18211831
</inline-list>
18221832

1833+
<infobox variant="warning">
1834+
In Thinc v8.2+, MXNet support is not enabled by default. To enable MXNet:
1835+
1836+
```python
1837+
from thinc.api import enable_mxnet
1838+
enable_mxnet()
1839+
```
1840+
1841+
</infobox>
1842+
18231843
Wrap a [MXNet](https://mxnet.apache.org/) model, so that it has the same API as
18241844
Thinc models. To optimize the model, you'll need to create a MXNet optimizer and
18251845
call `optimizer.step()` after each batch. To allow maximum flexibility, the

website/docs/api-util.md

+8
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ Converts a class vector (integers) to binary class matrix. Based on
141141
| `label_smoothing` | <tt>float</tt> | Smoothing-coefficient for label-smoothing. |
142142
| **RETURNS** | <tt>Floats2d</tt> | A binary matrix representation of the input. The axis representing the classes is placed last. |
143143

144+
### enable_mxnet {#enable_mxnet tag="function" new="8.2.0"}
145+
146+
Import and enable internal support for MXNet.
147+
148+
### enable_tensorflow {#enable_tensorflow tag="function" new="8.2.0"}
149+
150+
Import and enable internal support for TensorFlow.
151+
144152
### xp2torch {#xp2torch tag="function"}
145153

146154
Convert a `numpy` or `cupy` tensor to a PyTorch tensor.

website/docs/usage-frameworks.md

+10
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ Y, backprop = model(X, is_train=True)
8181
dX = backprop(Y)
8282
```
8383

84+
<infobox variant="warning">
85+
In Thinc v8.2+, TensorFlow support is not enabled by default. To enable TensorFlow:
86+
87+
```python
88+
from thinc.api import enable_tensorflow
89+
enable_tensorflow()
90+
```
91+
92+
</infobox>
93+
8494
```python
8595
### TensorFlow Example {highlight="6"}
8696
from thinc.api import TensorFlowWrapper, chain, Linear

0 commit comments

Comments
 (0)