Fix misleading 'only for image models' error for Qwen3-VL when torchvision is missing (#6525)

* Fix misleading 'only for image models' error for Qwen3-VL when torchvision is missing

transformers >= 5.4 hard-requires torchvision for VLM image/video processors and
no longer falls back to a slow processor. Without torchvision the processor load
raises ImportError, unsloth degrades to a text-only tokenizer, and the vision data
collator later fails with 'UnslothVisionDataCollator is only for image models!'.

Detect this case at load time and raise a clear, actionable error pointing at the
missing torchvision dependency instead.

Fixes unslothai/unsloth#4202

* Apply kwarg-spacing format hook to vision torchvision guard (pre-commit)

* Make torchvision-missing detection precise: check availability first, match specific error text

* Tighten code comments (no logic change)

* Make missing-torchvision VLM error version-agnostic

The raise also fires on transformers 4.57.x for VLMs with a video processor
(Qwen2.5-VL, Qwen3-VL), where AutoVideoProcessor requires torchvision. The old
message claimed 'transformers >= 5.4 requires torchvision', which is inaccurate
on 4.57.x. Reword to state torchvision is required for this model's vision
processors without a version-specific claim.

---------

Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
This commit is contained in:
Daniel Han 2026-06-23 01:28:09 -07:00 committed by GitHub
commit 7b208bc35c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 3 deletions

View file

@ -0,0 +1,30 @@
"""Regression test for unsloth#4202: detect missing torchvision so the loader can
surface the real cause instead of a misleading collator error."""
import importlib.util
from unittest import mock
from unsloth.models.vision import _missing_torchvision_error
def test_error_text_mentions_torchvision_is_detected():
err = ImportError("Qwen3VLVideoProcessor requires the Torchvision library but ...")
assert _missing_torchvision_error(err) is True
def test_torchvision_missing_is_detected_without_error():
with mock.patch.object(importlib.util, "find_spec", return_value = None):
assert _missing_torchvision_error(None) is True
def test_torchvision_present_unrelated_error_is_not_flagged():
sentinel = object()
with mock.patch.object(importlib.util, "find_spec", return_value = sentinel):
assert _missing_torchvision_error(ValueError("unrelated")) is False
assert _missing_torchvision_error(None) is False
def test_matches_real_environment():
# find_spec is the source of truth when no error is supplied.
expected = importlib.util.find_spec("torchvision") is None
assert _missing_torchvision_error(None) is expected

View file

@ -449,6 +449,23 @@ def unsloth_base_fast_generate(self, *args, **kwargs):
return output return output
def _missing_torchvision_error(error = None):
"""True if a VLM processor failed to load due to missing torchvision (#4202).
Checks availability directly first, then only the specific torchvision-required
error text (not any incidental "torchvision" substring like a model path)."""
import importlib.util
if importlib.util.find_spec("torchvision") is None:
return True
if error is not None:
error_str = str(error).lower()
return (
"requires the torchvision" in error_str or "no module named 'torchvision'" in error_str
)
return False
def _construct_vlm_processor_fallback(tokenizer_name, model_type, token, trust_remote_code): def _construct_vlm_processor_fallback(tokenizer_name, model_type, token, trust_remote_code):
"""Construct a VLM processor manually when AutoProcessor.from_pretrained fails. """Construct a VLM processor manually when AutoProcessor.from_pretrained fails.
@ -1154,6 +1171,7 @@ class FastBaseModel:
except Exception: except Exception:
pass pass
_processor_load_error = None
if (whisper_language and whisper_task) or auto_model.__name__.endswith( if (whisper_language and whisper_task) or auto_model.__name__.endswith(
"ForConditionalGeneration" "ForConditionalGeneration"
): ):
@ -1166,7 +1184,8 @@ class FastBaseModel:
task = whisper_task, task = whisper_task,
trust_remote_code = trust_remote_code, trust_remote_code = trust_remote_code,
) )
except Exception: except Exception as e:
_processor_load_error = e
tokenizer = None tokenizer = None
else: else:
try: try:
@ -1176,7 +1195,8 @@ class FastBaseModel:
token = token, token = token,
trust_remote_code = trust_remote_code, trust_remote_code = trust_remote_code,
) )
except: except Exception as e:
_processor_load_error = e
tokenizer = get_auto_processor( tokenizer = get_auto_processor(
tokenizer_name, tokenizer_name,
padding_side = "left", padding_side = "left",
@ -1200,7 +1220,16 @@ class FastBaseModel:
) )
if _fallback is not None: if _fallback is not None:
tokenizer = _fallback tokenizer = _fallback
if tokenizer is None: # Missing torchvision silently degrades the VLM processor to a text-only
# tokenizer; surface the real cause instead of the later collator error (#4202).
if tokenizer is None or not hasattr(tokenizer, "image_processor"):
if _missing_torchvision_error(_processor_load_error):
raise ImportError(
f"Unsloth: Could not load the vision processor for `{tokenizer_name}` "
"because torchvision is not installed. transformers requires torchvision "
"for this model's vision (image/video) processors. Please install it, "
"e.g. `pip install torchvision`."
)
import sys import sys
print( print(
f"Unsloth: Warning - VLM processor fallback returned None for model_type={model_type_arch}", f"Unsloth: Warning - VLM processor fallback returned None for model_type={model_type_arch}",