From 770714acc558ab6a8a15fdb867fe453bd1931a0e Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Thu, 14 May 2026 05:14:21 -0700 Subject: [PATCH] import_fixes + drift detectors: cover transformers 5.x drift (#5423) PR #5414's drift detectors and the corresponding import_fixes helpers were written against transformers 4.x. The Repo tests (CPU) step (which installs transformers>=4.51,<5.5 and currently resolves to 5.4.0) surfaces three real predicate gaps: * test_pretrained_model_enable_input_require_grads_uses_old_pattern fires DRIFT DETECTED whenever the source contains "for module in self.modules()". But the unsloth replacement that patch_enable_input_require_grads installs ALSO uses that pattern -- deliberately, just wrapped in try / except NotImplementedError. So the predicate cannot distinguish broken upstream from the working patch. Accept either pre-HF#41993 shape (no self.modules() loop) or the post-patch shape (loop + NotImplementedError handler). * test_transformers_torchcodec_available_flag_is_present asserts the pre-5.x module-level _torchcodec_available flag. transformers 5 replaced it with an lru_cache'd is_torchcodec_available() callable. Accept either symbol. Also update disable_torchcodec_if_broken to actually disable on 5.x: clear the cache and rebind the function to return False. Local verification: * transformers 4.57.6 + trl 0.25.1 + peft 0.19.1 + triton 3.5.1 + vllm 0.15.1+cu130 (the Core HF=4.57.6 cell shape): 18 passed. * transformers 5.8.1 + peft 0.19.1 + torch 2.9 CPU (the Repo tests (CPU) shape, drop trl / vllm / datasets / xformers): 12 passed, 6 skipped on missing optional libs, 0 failed. PR #5376's Repo tests (CPU) failure was a triple: - triton + enable_input_require_grads: fixed by merging current main (PR #5421's relaxed triton predicate + conftest 'import unsloth'). - torchcodec: fixed by THIS PR. --- tests/test_import_fixes_drift.py | 46 ++++++++++++++++++++------------ unsloth/import_fixes.py | 23 +++++++++++++++- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/tests/test_import_fixes_drift.py b/tests/test_import_fixes_drift.py index 7f8402135f..f90556bf66 100644 --- a/tests/test_import_fixes_drift.py +++ b/tests/test_import_fixes_drift.py @@ -175,10 +175,12 @@ def test_trl_cached_available_flags_are_not_tuples(): def test_pretrained_model_enable_input_require_grads_uses_old_pattern(): - """``patch_enable_input_require_grads`` (import_fixes.py 609-670). - HF PR #41993 rewrote enable_input_require_grads to iterate + """``patch_enable_input_require_grads`` (import_fixes.py 609-670). HF + PR #41993 rewrote enable_input_require_grads to iterate ``self.modules()`` and call ``get_input_embeddings`` on every - submodule; vision submodules then raise NotImplementedError.""" + submodule; vision submodules then raise NotImplementedError. Healthy + state: either the upstream rewrite isn't present (pre-HF#41993), OR + the patch installed a NotImplementedError-tolerant replacement.""" pytest.importorskip("transformers") from transformers import PreTrainedModel @@ -187,24 +189,34 @@ def test_pretrained_model_enable_input_require_grads_uses_old_pattern(): except Exception as exc: pytest.skip(f"could not getsource(enable_input_require_grads): {exc!r}") - if "for module in self.modules()" in src: - pytest.fail( - "DRIFT DETECTED: PreTrainedModel.enable_input_require_grads now " - "iterates self.modules() (post HF#41993). " - "patch_enable_input_require_grads has to install a " - "NotImplementedError-tolerant replacement." - ) + if "for module in self.modules()" not in src: + return # healthy: pre-HF#41993 shape + if "NotImplementedError" in src: + return # healthy: unsloth's tolerant replacement is installed + + pytest.fail( + "DRIFT DETECTED: PreTrainedModel.enable_input_require_grads now " + "iterates self.modules() (post HF#41993) and has NOT been " + "wrapped by patch_enable_input_require_grads; vision submodules " + "(e.g. GLM V4.6's self.visual) will raise NotImplementedError " + "from get_input_embeddings and crash the whole call." + ) def test_transformers_torchcodec_available_flag_is_present(): - """``disable_torchcodec_if_broken`` (import_fixes.py 1291-1317). - Flips ``transformers.utils.import_utils._torchcodec_available`` to - False when torchcodec is installed but its FFmpeg deps are broken.""" + """``disable_torchcodec_if_broken`` (import_fixes.py 1291-1317). Needs + either the pre-5.x module-level ``_torchcodec_available`` flag, or + the 5.x ``is_torchcodec_available`` public function; one of the two + is the patch site the fix monkey-patches when FFmpeg is missing.""" tf_iu = pytest.importorskip("transformers.utils.import_utils") - assert hasattr(tf_iu, "_torchcodec_available"), ( - "transformers.utils.import_utils._torchcodec_available was " - "removed/renamed upstream; disable_torchcodec_if_broken can no " - "longer disable a broken torchcodec install." + has_flag = hasattr(tf_iu, "_torchcodec_available") + has_func = callable(getattr(tf_iu, "is_torchcodec_available", None)) + assert has_flag or has_func, ( + "transformers.utils.import_utils dropped both " + "``_torchcodec_available`` (pre-5.x) AND " + "``is_torchcodec_available`` (>=5.x); " + "disable_torchcodec_if_broken can no longer disable a broken " + "torchcodec install." ) diff --git a/unsloth/import_fixes.py b/unsloth/import_fixes.py index e6d81b2f8c..8476edc40a 100644 --- a/unsloth/import_fixes.py +++ b/unsloth/import_fixes.py @@ -1298,6 +1298,13 @@ def disable_torchcodec_if_broken(): This function tests if torchcodec can actually load and if not, patches transformers to think torchcodec is unavailable so it falls back to librosa. + + Two shapes to cover: + * transformers < 5: a module-level ``_torchcodec_available`` flag + cached in ``transformers.utils.import_utils``; flip it to False. + * transformers >= 5: a public ``is_torchcodec_available()`` callable + wrapped with ``functools.lru_cache``; replace it with a stub that + returns False and clear the cache so subsequent callers see it. """ try: import importlib.util @@ -1311,11 +1318,25 @@ def disable_torchcodec_if_broken(): # torchcodec cannot load - disable it in transformers try: import transformers.utils.import_utils as tf_import_utils + except ImportError: + return + # transformers < 5 path: module-level cached flag. + try: tf_import_utils._torchcodec_available = False - except (ImportError, AttributeError): + except AttributeError: pass + # transformers >= 5 path: public lru_cache'd function. Clear any + # cached True result then rebind to a stub that returns False. + is_avail = getattr(tf_import_utils, "is_torchcodec_available", None) + if is_avail is not None: + try: + is_avail.cache_clear() + except AttributeError: + pass + tf_import_utils.is_torchcodec_available = lambda: False + def disable_broken_wandb(): """Disable wandb if it's installed but cannot actually import.