diff --git a/RELEASES.md b/RELEASES.md index abb8f04c8..3447518bc 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,6 +15,8 @@ #### Closed issues + +- Probing for triton in `ot.backend` no longer propagates errors other than `ImportError`, so a broken native triton install can no longer stop `import ot` or silently disable the torch backend (PR #865, follow-up to PR #839 and Issue #816) - Allow `NumpyBackend.seed` to adopt an existing `np.random.RandomState` instance and remove NumPy-specific random sampling paths in sliced utilities (PR #849, Issue #848) - Remove a leftover debug `print` from `ot.utils.projection_sparse_simplex` with `axis=1`, and make the `ot.datasets.make_gauss_hd` docstring a raw string so importing `ot` no longer emits a `SyntaxWarning` (PR #860) - Fix the value reported by the entropic unbalanced OT solvers (`ot.unbalanced.sinkhorn_unbalanced`, `sinkhorn_unbalanced2`, `sinkhorn_knopp_unbalanced`, `sinkhorn_stabilized_unbalanced` and `sinkhorn_unbalanced_translation_invariant`, also exposed as `ot.solve(..., reg=..., unbalanced=...).value`): every penalization is now evaluated with the generalized KL divergence, and with `reg_type="entropy"` the constant `dim_a * dim_b` is removed so that the reported value matches the documented negative entropy regularizer. The transport plans are unchanged. `reg_type` is now handled case insensitively by all the methods and an unknown value raises a `ValueError` instead of silently falling back to `"kl"`. The docstrings now state the generalized KL convention, the regularizer `Omega_reg_type(gamma, c)` with its two cases, the keys of the `log` dictionary and the multi-histogram behaviour (PR #874) diff --git a/ot/backend.py b/ot/backend.py index f55938a93..52ad26510 100644 --- a/ot/backend.py +++ b/ot/backend.py @@ -117,13 +117,16 @@ # first use of a feature that needs it (constructing an optimizer is # enough), which would otherwise happen after TensorFlow is loaded. # See https://github.com/PythonOT/POT/issues/816 - if not os.environ.get(DISABLE_TF_KEY, False) and ( - importlib.util.find_spec("tensorflow") is not None - ): - try: + # Probing must never be fatal: a broken or partial triton install must + # not stop `import ot`, and must not silently disable the torch backend + # either, so this catches more than ImportError. + try: + if not os.environ.get(DISABLE_TF_KEY, False) and ( + importlib.util.find_spec("tensorflow") is not None + ): import triton # noqa: F401 - except ImportError: - pass + except Exception: # pragma: no cover - depends on the installation + pass except ImportError: torch = False torch_type = float diff --git a/test/test_backend.py b/test/test_backend.py index 5a4570578..0113d0d08 100644 --- a/test/test_backend.py +++ b/test/test_backend.py @@ -7,6 +7,7 @@ # License: MIT License import importlib.util +import os import subprocess import sys @@ -993,3 +994,38 @@ def test_no_cuda_context_for_cpu_only_work(): f"interpreter exited with returncode {result.returncode}: " f"{result.stderr.decode(errors='replace')[-2000:]}" ) + + +@pytest.mark.skipif(not torch, reason="Requires torch") +def test_broken_triton_does_not_break_import(tmp_path): + """Probing for triton must never be fatal (see issue #816). + + ot.backend imports triton eagerly so that it loads before TensorFlow. A + broken native install raises something other than ImportError, which must + not stop `import ot` nor disable the torch backend. + """ + (tmp_path / "triton.py").write_text( + 'raise OSError("libtriton.so: cannot open shared object file")\n' + ) + # a stub is enough: the probe only needs find_spec("tensorflow") to succeed + tf_stub = tmp_path / "tensorflow" + tf_stub.mkdir() + (tf_stub / "__init__.py").write_text("class Tensor:\n pass\n") + (tf_stub / "experimental").mkdir() + (tf_stub / "experimental" / "__init__.py").write_text('raise ImportError("stub")\n') + + env = dict(os.environ) + env["PYTHONPATH"] = str(tmp_path) + os.pathsep + env.get("PYTHONPATH", "") + result = subprocess.run( + [ + sys.executable, + "-c", + "import ot.backend as b\n" + "assert b.torch is not False, 'torch backend was disabled by the probe'\n" + "print('ok')\n", + ], + capture_output=True, + env=env, + ) + assert result.returncode == 0, result.stderr.decode(errors="replace")[-2000:] + assert b"ok" in result.stdout