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:
parent
f74c48eb58
commit
7b208bc35c
2 changed files with 62 additions and 3 deletions
30
tests/test_missing_torchvision_vlm.py
Normal file
30
tests/test_missing_torchvision_vlm.py
Normal 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
|
||||
|
|
@ -449,6 +449,23 @@ def unsloth_base_fast_generate(self, *args, **kwargs):
|
|||
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):
|
||||
"""Construct a VLM processor manually when AutoProcessor.from_pretrained fails.
|
||||
|
||||
|
|
@ -1154,6 +1171,7 @@ class FastBaseModel:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
_processor_load_error = None
|
||||
if (whisper_language and whisper_task) or auto_model.__name__.endswith(
|
||||
"ForConditionalGeneration"
|
||||
):
|
||||
|
|
@ -1166,7 +1184,8 @@ class FastBaseModel:
|
|||
task = whisper_task,
|
||||
trust_remote_code = trust_remote_code,
|
||||
)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
_processor_load_error = e
|
||||
tokenizer = None
|
||||
else:
|
||||
try:
|
||||
|
|
@ -1176,7 +1195,8 @@ class FastBaseModel:
|
|||
token = token,
|
||||
trust_remote_code = trust_remote_code,
|
||||
)
|
||||
except:
|
||||
except Exception as e:
|
||||
_processor_load_error = e
|
||||
tokenizer = get_auto_processor(
|
||||
tokenizer_name,
|
||||
padding_side = "left",
|
||||
|
|
@ -1200,7 +1220,16 @@ class FastBaseModel:
|
|||
)
|
||||
if _fallback is not None:
|
||||
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
|
||||
print(
|
||||
f"Unsloth: Warning - VLM processor fallback returned None for model_type={model_type_arch}",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue