mirror of
https://github.com/unslothai/unsloth
synced 2026-04-21 13:37:39 +00:00
split venv_t5 into tiered 5.3.0/5.5.0 and fix trust_remote_code (#4878)
* split venv_t5 into venv_t5_530 and venv_t5_550 for tiered transformers 5.x support * fix bfloat16 crash on T4 for FORCE_FLOAT32 models and disable trust_remote_code auto-enable for native t5 models * revert FORCE_FLOAT32 dtype change * restrict trust_remote_code auto-enable to Nemotron models only * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * use config.json model_type for tier detection, add unsloth/nvidia namespace guard * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Revert "[pre-commit.ci] auto fixes from pre-commit.com hooks" This reverts commitfb43d468e2. * Revert "use config.json model_type for tier detection, add unsloth/nvidia namespace guard" This reverts commitfc49ae2453. * add unsloth/nvidia namespace guard to Nemotron trust_remote_code auto-enable * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * reorder tier checks: all substring matches before config.json fetches * extract shared activate_transformers_for_subprocess into transformers_version.py * narrow Nemotron trust_remote_code to nemotron_h/nemotron-3-nano, add to export worker * clean venv_t5 dirs before re-install in setup.sh, clarify version alias comment * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * run venv_t5 migration outside deps fast-path gate in both setup scripts --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
1d8160376e
commit
f801e59c29
8 changed files with 601 additions and 216 deletions
|
|
@ -30,37 +30,15 @@ logger = get_logger(__name__)
|
|||
|
||||
|
||||
def _activate_transformers_version(model_name: str) -> None:
|
||||
"""Activate the correct transformers version BEFORE any ML imports.
|
||||
|
||||
If the model needs transformers 5.x, prepend the pre-installed .venv_t5/
|
||||
directory to sys.path. Otherwise do nothing (default 4.57.x in .venv/).
|
||||
"""
|
||||
"""Activate the correct transformers version BEFORE any ML imports."""
|
||||
# Ensure backend is on path for utils imports
|
||||
backend_path = str(Path(__file__).resolve().parent.parent.parent)
|
||||
if backend_path not in sys.path:
|
||||
sys.path.insert(0, backend_path)
|
||||
|
||||
from utils.transformers_version import (
|
||||
needs_transformers_5,
|
||||
_resolve_base_model,
|
||||
_ensure_venv_t5_exists,
|
||||
_VENV_T5_DIR,
|
||||
)
|
||||
from utils.transformers_version import activate_transformers_for_subprocess
|
||||
|
||||
resolved = _resolve_base_model(model_name)
|
||||
if needs_transformers_5(resolved):
|
||||
if not _ensure_venv_t5_exists():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}"
|
||||
)
|
||||
if _VENV_T5_DIR not in sys.path:
|
||||
sys.path.insert(0, _VENV_T5_DIR)
|
||||
logger.info("Activated transformers 5.x from %s", _VENV_T5_DIR)
|
||||
# Propagate to child subprocesses (e.g. GGUF converter)
|
||||
_pp = os.environ.get("PYTHONPATH", "")
|
||||
os.environ["PYTHONPATH"] = _VENV_T5_DIR + (os.pathsep + _pp if _pp else "")
|
||||
else:
|
||||
logger.info("Using default transformers (4.57.x) for %s", model_name)
|
||||
activate_transformers_for_subprocess(model_name)
|
||||
|
||||
|
||||
def _send_response(resp_queue: Any, response: dict) -> None:
|
||||
|
|
@ -78,6 +56,19 @@ def _handle_load(backend, cmd: dict, resp_queue: Any) -> None:
|
|||
load_in_4bit = cmd.get("load_in_4bit", True)
|
||||
trust_remote_code = cmd.get("trust_remote_code", False)
|
||||
|
||||
# Auto-enable trust_remote_code for NemotronH/Nano models.
|
||||
if not trust_remote_code:
|
||||
_NEMOTRON_TRUST_SUBSTRINGS = ("nemotron_h", "nemotron-h", "nemotron-3-nano")
|
||||
_cp_lower = checkpoint_path.lower()
|
||||
if any(sub in _cp_lower for sub in _NEMOTRON_TRUST_SUBSTRINGS) and (
|
||||
_cp_lower.startswith("unsloth/") or _cp_lower.startswith("nvidia/")
|
||||
):
|
||||
trust_remote_code = True
|
||||
logger.info(
|
||||
"Auto-enabled trust_remote_code for Nemotron model: %s",
|
||||
checkpoint_path,
|
||||
)
|
||||
|
||||
try:
|
||||
_send_response(
|
||||
resp_queue,
|
||||
|
|
|
|||
|
|
@ -34,37 +34,15 @@ from utils.hardware import apply_gpu_ids
|
|||
|
||||
|
||||
def _activate_transformers_version(model_name: str) -> None:
|
||||
"""Activate the correct transformers version BEFORE any ML imports.
|
||||
|
||||
If the model needs transformers 5.x, prepend the pre-installed .venv_t5/
|
||||
directory to sys.path. Otherwise do nothing (default 4.57.x in .venv/).
|
||||
"""
|
||||
"""Activate the correct transformers version BEFORE any ML imports."""
|
||||
# Ensure backend is on path for utils imports
|
||||
backend_path = str(Path(__file__).resolve().parent.parent.parent)
|
||||
if backend_path not in sys.path:
|
||||
sys.path.insert(0, backend_path)
|
||||
|
||||
from utils.transformers_version import (
|
||||
needs_transformers_5,
|
||||
_resolve_base_model,
|
||||
_ensure_venv_t5_exists,
|
||||
_VENV_T5_DIR,
|
||||
)
|
||||
from utils.transformers_version import activate_transformers_for_subprocess
|
||||
|
||||
resolved = _resolve_base_model(model_name)
|
||||
if needs_transformers_5(resolved):
|
||||
if not _ensure_venv_t5_exists():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}"
|
||||
)
|
||||
if _VENV_T5_DIR not in sys.path:
|
||||
sys.path.insert(0, _VENV_T5_DIR)
|
||||
logger.info("Activated transformers 5.x from %s", _VENV_T5_DIR)
|
||||
# Propagate to child subprocesses (e.g. GGUF converter)
|
||||
_pp = os.environ.get("PYTHONPATH", "")
|
||||
os.environ["PYTHONPATH"] = _VENV_T5_DIR + (os.pathsep + _pp if _pp else "")
|
||||
else:
|
||||
logger.info("Using default transformers (4.57.x) for %s", model_name)
|
||||
activate_transformers_for_subprocess(model_name)
|
||||
|
||||
|
||||
def _decode_image(image_base64: str):
|
||||
|
|
@ -309,19 +287,21 @@ def _handle_load(backend, config: dict, resp_queue: Any) -> None:
|
|||
except Exception as e:
|
||||
logger.warning("Could not read adapter_config.json: %s", e)
|
||||
|
||||
# Auto-enable trust_remote_code for unsloth/* transformers 5.x models
|
||||
# (matches the training worker logic in core/training/worker.py)
|
||||
# Auto-enable trust_remote_code for NemotronH/Nano models only.
|
||||
# NemotronH has config parsing bugs requiring trust_remote_code=True.
|
||||
# Other transformers 5.x models are native and do NOT need it.
|
||||
# NOTE: Must NOT match Llama-Nemotron (standard Llama architecture).
|
||||
_NEMOTRON_TRUST_SUBSTRINGS = ("nemotron_h", "nemotron-h", "nemotron-3-nano")
|
||||
trust_remote_code = config.get("trust_remote_code", False)
|
||||
if not trust_remote_code:
|
||||
from utils.transformers_version import needs_transformers_5
|
||||
|
||||
model_name = config["model_name"]
|
||||
if needs_transformers_5(model_name) and model_name.lower().startswith(
|
||||
"unsloth/"
|
||||
_mn_lower = model_name.lower()
|
||||
if any(sub in _mn_lower for sub in _NEMOTRON_TRUST_SUBSTRINGS) and (
|
||||
_mn_lower.startswith("unsloth/") or _mn_lower.startswith("nvidia/")
|
||||
):
|
||||
trust_remote_code = True
|
||||
logger.info(
|
||||
"Auto-enabled trust_remote_code for unsloth/* transformers 5.x model: %s",
|
||||
"Auto-enabled trust_remote_code for Nemotron model: %s",
|
||||
model_name,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -306,37 +306,15 @@ def _ensure_mamba_ssm(event_queue: Any, model_name: str) -> None:
|
|||
|
||||
|
||||
def _activate_transformers_version(model_name: str) -> None:
|
||||
"""Activate the correct transformers version BEFORE any ML imports.
|
||||
|
||||
If the model needs transformers 5.x, prepend the pre-installed .venv_t5/
|
||||
directory to sys.path. Otherwise do nothing (default 4.57.x in .venv/).
|
||||
"""
|
||||
"""Activate the correct transformers version BEFORE any ML imports."""
|
||||
# Ensure backend is on path for utils imports
|
||||
backend_path = str(Path(__file__).resolve().parent.parent.parent)
|
||||
if backend_path not in sys.path:
|
||||
sys.path.insert(0, backend_path)
|
||||
|
||||
from utils.transformers_version import (
|
||||
needs_transformers_5,
|
||||
_resolve_base_model,
|
||||
_ensure_venv_t5_exists,
|
||||
_VENV_T5_DIR,
|
||||
)
|
||||
from utils.transformers_version import activate_transformers_for_subprocess
|
||||
|
||||
resolved = _resolve_base_model(model_name)
|
||||
if needs_transformers_5(resolved):
|
||||
if not _ensure_venv_t5_exists():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}"
|
||||
)
|
||||
if _VENV_T5_DIR not in sys.path:
|
||||
sys.path.insert(0, _VENV_T5_DIR)
|
||||
logger.info("Activated transformers 5.x from %s", _VENV_T5_DIR)
|
||||
# Propagate to child subprocesses (e.g. GGUF converter)
|
||||
_pp = os.environ.get("PYTHONPATH", "")
|
||||
os.environ["PYTHONPATH"] = _VENV_T5_DIR + (os.pathsep + _pp if _pp else "")
|
||||
else:
|
||||
logger.info("Using default transformers (4.57.x) for %s", model_name)
|
||||
activate_transformers_for_subprocess(model_name)
|
||||
|
||||
|
||||
def run_training_process(
|
||||
|
|
@ -386,25 +364,22 @@ def run_training_process(
|
|||
)
|
||||
return
|
||||
|
||||
# ── 1a. Auto-enable trust_remote_code for unsloth/* transformers 5.x models ──
|
||||
# Some newer architectures (e.g. NemotronH) have config parsing bugs in
|
||||
# transformers that require trust_remote_code=True as a workaround.
|
||||
# Only auto-enable for unsloth/* prefixed models (trusted source).
|
||||
# Exclude Gemma 4 since it is a native transformers 5.5 model and
|
||||
# trust_remote_code=True would bypass the compiler (disabling fused CE).
|
||||
from utils.transformers_version import needs_transformers_5
|
||||
|
||||
# ── 1a. Auto-enable trust_remote_code for NemotronH/Nano models ──
|
||||
# NemotronH has config parsing bugs in transformers that require
|
||||
# trust_remote_code=True as a workaround. Other transformers 5.x models
|
||||
# (Qwen3.5, Gemma 4, etc.) are native and do NOT need it — enabling it
|
||||
# bypasses the compiler (disabling fused CE).
|
||||
# NOTE: Must NOT match Llama-Nemotron (standard Llama architecture).
|
||||
_NEMOTRON_TRUST_SUBSTRINGS = ("nemotron_h", "nemotron-h", "nemotron-3-nano")
|
||||
_lowered = model_name.lower()
|
||||
_is_native_t5 = any(x in _lowered for x in ("gemma-4", "gemma4"))
|
||||
if (
|
||||
needs_transformers_5(model_name)
|
||||
and _lowered.startswith("unsloth/")
|
||||
and not _is_native_t5
|
||||
any(sub in _lowered for sub in _NEMOTRON_TRUST_SUBSTRINGS)
|
||||
and (_lowered.startswith("unsloth/") or _lowered.startswith("nvidia/"))
|
||||
and not config.get("trust_remote_code", False)
|
||||
):
|
||||
config["trust_remote_code"] = True
|
||||
logger.info(
|
||||
"Auto-enabled trust_remote_code for unsloth/* transformers 5.x model: %s",
|
||||
"Auto-enabled trust_remote_code for Nemotron model: %s",
|
||||
model_name,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ sys.modules.setdefault("loggers", _loggers_stub)
|
|||
from utils.transformers_version import (
|
||||
_resolve_base_model,
|
||||
_check_tokenizer_config_needs_v5,
|
||||
_check_config_needs_550,
|
||||
_tokenizer_class_cache,
|
||||
_config_needs_550_cache,
|
||||
needs_transformers_5,
|
||||
get_transformers_tier,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -188,3 +191,148 @@ class TestNeedsTransformers5:
|
|||
# We test the full resolution chain here:
|
||||
resolved = _resolve_base_model(str(tmp_path))
|
||||
assert needs_transformers_5(resolved) is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _check_config_needs_550 — config.json architecture/model_type check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCheckConfigNeeds550:
|
||||
"""Tests for _check_config_needs_550() local config.json checks."""
|
||||
|
||||
def setup_method(self):
|
||||
_config_needs_550_cache.clear()
|
||||
|
||||
def test_gemma4_architecture(self, tmp_path: Path):
|
||||
"""config.json with Gemma4ForConditionalGeneration should return True."""
|
||||
cfg = {
|
||||
"architectures": ["Gemma4ForConditionalGeneration"],
|
||||
"model_type": "gemma4",
|
||||
}
|
||||
(tmp_path / "config.json").write_text(json.dumps(cfg))
|
||||
|
||||
assert _check_config_needs_550(str(tmp_path)) is True
|
||||
|
||||
def test_gemma4_model_type_only(self, tmp_path: Path):
|
||||
"""config.json with model_type=gemma4 (no architectures) should return True."""
|
||||
cfg = {"model_type": "gemma4"}
|
||||
(tmp_path / "config.json").write_text(json.dumps(cfg))
|
||||
|
||||
assert _check_config_needs_550(str(tmp_path)) is True
|
||||
|
||||
def test_llama_architecture(self, tmp_path: Path):
|
||||
"""config.json with LlamaForCausalLM should return False."""
|
||||
cfg = {"architectures": ["LlamaForCausalLM"], "model_type": "llama"}
|
||||
(tmp_path / "config.json").write_text(json.dumps(cfg))
|
||||
|
||||
assert _check_config_needs_550(str(tmp_path)) is False
|
||||
|
||||
def test_no_config_json(self, tmp_path: Path):
|
||||
"""Missing config.json should return False (fail-open)."""
|
||||
# Patch network call to avoid real fetch
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
mock_urlopen.side_effect = Exception("no network")
|
||||
assert _check_config_needs_550(str(tmp_path)) is False
|
||||
|
||||
def test_result_is_cached(self, tmp_path: Path):
|
||||
"""Subsequent calls should use the cache."""
|
||||
cfg = {"architectures": ["Gemma4ForConditionalGeneration"]}
|
||||
(tmp_path / "config.json").write_text(json.dumps(cfg))
|
||||
|
||||
key = str(tmp_path)
|
||||
_check_config_needs_550(key)
|
||||
assert key in _config_needs_550_cache
|
||||
assert _config_needs_550_cache[key] is True
|
||||
|
||||
def test_local_file_skips_network(self, tmp_path: Path):
|
||||
"""When local config.json exists, no network request should be made."""
|
||||
cfg = {"architectures": ["LlamaForCausalLM"]}
|
||||
(tmp_path / "config.json").write_text(json.dumps(cfg))
|
||||
|
||||
with patch("urllib.request.urlopen") as mock_urlopen:
|
||||
_check_config_needs_550(str(tmp_path))
|
||||
mock_urlopen.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_transformers_tier — tier detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetTransformersTier:
|
||||
"""Tests for get_transformers_tier() tiered version detection."""
|
||||
|
||||
def setup_method(self):
|
||||
_tokenizer_class_cache.clear()
|
||||
_config_needs_550_cache.clear()
|
||||
|
||||
def test_gemma4_substring_returns_550(self):
|
||||
assert get_transformers_tier("google/gemma-4-E2B-it") == "550"
|
||||
|
||||
def test_gemma4_alt_substring_returns_550(self):
|
||||
assert get_transformers_tier("unsloth/gemma4-E4B-it") == "550"
|
||||
|
||||
def test_gemma4_config_json_returns_550(self, tmp_path: Path):
|
||||
"""Local checkpoint with Gemma4 architecture → 550."""
|
||||
cfg = {
|
||||
"architectures": ["Gemma4ForConditionalGeneration"],
|
||||
"model_type": "gemma4",
|
||||
}
|
||||
(tmp_path / "config.json").write_text(json.dumps(cfg))
|
||||
|
||||
assert get_transformers_tier(str(tmp_path)) == "550"
|
||||
|
||||
def test_qwen35_returns_530(self):
|
||||
with patch(
|
||||
"utils.transformers_version._check_config_needs_550",
|
||||
return_value = False,
|
||||
):
|
||||
assert get_transformers_tier("Qwen/Qwen3.5-9B") == "530"
|
||||
|
||||
def test_ministral_returns_530(self):
|
||||
with patch(
|
||||
"utils.transformers_version._check_config_needs_550",
|
||||
return_value = False,
|
||||
):
|
||||
assert (
|
||||
get_transformers_tier("mistralai/Ministral-3-8B-Instruct-2512") == "530"
|
||||
)
|
||||
|
||||
def test_llama_returns_default(self):
|
||||
with (
|
||||
patch(
|
||||
"utils.transformers_version._check_config_needs_550",
|
||||
return_value = False,
|
||||
),
|
||||
patch(
|
||||
"utils.transformers_version._check_tokenizer_config_needs_v5",
|
||||
return_value = False,
|
||||
),
|
||||
):
|
||||
assert get_transformers_tier("meta-llama/Llama-3-8B") == "default"
|
||||
|
||||
def test_550_checked_before_530(self):
|
||||
"""Ensure 5.5.0 is checked first — a model matching both should get 550."""
|
||||
# This shouldn't happen in practice, but verifies priority
|
||||
assert get_transformers_tier("gemma-4-model") == "550"
|
||||
|
||||
def test_needs_transformers_5_compat(self):
|
||||
"""needs_transformers_5 should return True for both 530 and 550 models."""
|
||||
assert needs_transformers_5("google/gemma-4-E2B-it") is True
|
||||
with patch(
|
||||
"utils.transformers_version._check_config_needs_550",
|
||||
return_value = False,
|
||||
):
|
||||
assert needs_transformers_5("Qwen/Qwen3.5-9B") is True
|
||||
with (
|
||||
patch(
|
||||
"utils.transformers_version._check_config_needs_550",
|
||||
return_value = False,
|
||||
),
|
||||
patch(
|
||||
"utils.transformers_version._check_tokenizer_config_needs_v5",
|
||||
return_value = False,
|
||||
),
|
||||
):
|
||||
assert needs_transformers_5("meta-llama/Llama-3-8B") is False
|
||||
|
|
|
|||
|
|
@ -493,8 +493,9 @@ _VLM_MODEL_TYPES = {
|
|||
"minicpmv",
|
||||
}
|
||||
|
||||
# Pre-computed .venv_t5 path and backend dir for subprocess version switching.
|
||||
_VENV_T5_DIR = str(Path.home() / ".unsloth" / "studio" / ".venv_t5")
|
||||
# Pre-computed .venv_t5 paths and backend dir for subprocess version switching.
|
||||
# Vision check uses 5.5.0 (newest, recognizes all architectures).
|
||||
_VENV_T5_DIR = str(Path.home() / ".unsloth" / "studio" / ".venv_t5_550")
|
||||
_BACKEND_DIR = str(Path(__file__).resolve().parent.parent.parent)
|
||||
|
||||
# Inline script executed in a subprocess with transformers 5.x activated.
|
||||
|
|
|
|||
|
|
@ -5,20 +5,25 @@
|
|||
Automatic transformers version switching.
|
||||
|
||||
Some newer model architectures (Ministral-3, GLM-4.7-Flash, Qwen3-30B-A3B MoE,
|
||||
tiny_qwen3_moe) require transformers>=5.3.0, while everything else needs the
|
||||
default 4.57.x that ships with Unsloth.
|
||||
tiny_qwen3_moe) require transformers>=5.3.0, while Gemma 4 models require
|
||||
transformers>=5.5.0. Everything else needs the default 4.57.x that ships
|
||||
with Unsloth.
|
||||
|
||||
Two separate target directories are maintained:
|
||||
- .venv_t5_530/ — transformers 5.3.0 (Ministral-3, GLM, Qwen3 MoE, etc.)
|
||||
- .venv_t5_550/ — transformers 5.5.0 (Gemma 4)
|
||||
|
||||
When loading a LoRA adapter with a custom name, we resolve the base model from
|
||||
``adapter_config.json`` and check *that* against the model list.
|
||||
|
||||
Strategy:
|
||||
Training and inference run in subprocesses that activate the correct version
|
||||
via sys.path (prepending .venv_t5/ for 5.x models). See:
|
||||
via sys.path (prepending the appropriate .venv_t5_*/ directory). See:
|
||||
- core/training/worker.py
|
||||
- core/inference/worker.py
|
||||
|
||||
For export (still in-process), ensure_transformers_version() does a lightweight
|
||||
sys.path swap using the same .venv_t5/ directory pre-installed by setup.sh.
|
||||
sys.path swap using the same directories pre-installed by setup.sh.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
|
|
@ -39,7 +44,7 @@ logger = get_logger(__name__)
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Lowercase substrings — if ANY appears anywhere in the lowered model name,
|
||||
# we need transformers 5.x.
|
||||
# we need transformers 5.3.0.
|
||||
TRANSFORMERS_5_MODEL_SUBSTRINGS: tuple[str, ...] = (
|
||||
"ministral-3-", # Ministral-3-{3,8,14}B-{Instruct,Reasoning,Base}-2512
|
||||
"glm-4.7-flash", # GLM-4.7-Flash
|
||||
|
|
@ -47,10 +52,23 @@ TRANSFORMERS_5_MODEL_SUBSTRINGS: tuple[str, ...] = (
|
|||
"qwen3.5", # Qwen3.5 family (35B-A3B, etc.)
|
||||
"qwen3-next", # Qwen3-Next and variants
|
||||
"tiny_qwen3_moe", # imdatta0/tiny_qwen3_moe_2.8B_0.7B
|
||||
)
|
||||
|
||||
# Lowercase substrings for models that require transformers 5.5.0 (checked first).
|
||||
TRANSFORMERS_550_MODEL_SUBSTRINGS: tuple[str, ...] = (
|
||||
"gemma-4", # Gemma-4 (E2B-it, E4B-it, 31B-it, 26B-A4B-it)
|
||||
"gemma4", # Gemma-4 alternate naming
|
||||
)
|
||||
|
||||
# Architecture classes / model_type values that require transformers 5.5.0.
|
||||
# Checked via config.json (local or HuggingFace).
|
||||
_TRANSFORMERS_550_ARCHITECTURES: set[str] = {
|
||||
"Gemma4ForConditionalGeneration",
|
||||
}
|
||||
_TRANSFORMERS_550_MODEL_TYPES: set[str] = {
|
||||
"gemma4",
|
||||
}
|
||||
|
||||
# Tokenizer classes that only exist in transformers>=5.x
|
||||
_TRANSFORMERS_5_TOKENIZER_CLASSES: set[str] = {
|
||||
"TokenizersBackend",
|
||||
|
|
@ -59,12 +77,61 @@ _TRANSFORMERS_5_TOKENIZER_CLASSES: set[str] = {
|
|||
# Cache for dynamic tokenizer_config.json lookups to avoid repeated fetches
|
||||
_tokenizer_class_cache: dict[str, bool] = {}
|
||||
|
||||
# Versions
|
||||
TRANSFORMERS_5_VERSION = "5.5.0"
|
||||
TRANSFORMERS_DEFAULT_VERSION = "4.57.6"
|
||||
# Cache for dynamic config.json lookups (architecture/model_type checks)
|
||||
_config_needs_550_cache: dict[str, bool] = {}
|
||||
|
||||
# Pre-installed directory for transformers 5.x — created by setup.sh / setup.ps1
|
||||
_VENV_T5_DIR = str(Path.home() / ".unsloth" / "studio" / ".venv_t5")
|
||||
# Versions
|
||||
TRANSFORMERS_550_VERSION = "5.5.0"
|
||||
TRANSFORMERS_530_VERSION = "5.3.0"
|
||||
TRANSFORMERS_DEFAULT_VERSION = "4.57.6"
|
||||
# Backwards-compat alias — points to 5.5.0 (the highest 5.x tier).
|
||||
# Consumers should prefer TRANSFORMERS_530_VERSION / TRANSFORMERS_550_VERSION.
|
||||
TRANSFORMERS_5_VERSION = TRANSFORMERS_550_VERSION
|
||||
|
||||
# Pre-installed directories — created by setup.sh / setup.ps1
|
||||
_VENV_T5_530_DIR = str(Path.home() / ".unsloth" / "studio" / ".venv_t5_530")
|
||||
_VENV_T5_550_DIR = str(Path.home() / ".unsloth" / "studio" / ".venv_t5_550")
|
||||
# Backwards-compat alias
|
||||
_VENV_T5_DIR = _VENV_T5_550_DIR
|
||||
|
||||
|
||||
def activate_transformers_for_subprocess(model_name: str) -> None:
|
||||
"""Activate the correct transformers version in a subprocess worker.
|
||||
|
||||
Call this BEFORE any ML imports. Resolves LoRA adapters to their base
|
||||
model, determines the required tier, and prepends the appropriate
|
||||
``.venv_t5_*`` directory to ``sys.path``. Also propagates the path
|
||||
via ``PYTHONPATH`` for child processes (e.g. GGUF converter).
|
||||
|
||||
Used by training, inference, and export workers.
|
||||
"""
|
||||
resolved = _resolve_base_model(model_name)
|
||||
tier = get_transformers_tier(resolved)
|
||||
|
||||
if tier == "550":
|
||||
if not _ensure_venv_t5_550_exists():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers 5.5.0: "
|
||||
f".venv_t5_550 missing at {_VENV_T5_550_DIR}"
|
||||
)
|
||||
if _VENV_T5_550_DIR not in sys.path:
|
||||
sys.path.insert(0, _VENV_T5_550_DIR)
|
||||
logger.info("Activated transformers 5.5.0 from %s", _VENV_T5_550_DIR)
|
||||
_pp = os.environ.get("PYTHONPATH", "")
|
||||
os.environ["PYTHONPATH"] = _VENV_T5_550_DIR + (os.pathsep + _pp if _pp else "")
|
||||
elif tier == "530":
|
||||
if not _ensure_venv_t5_530_exists():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers 5.3.0: "
|
||||
f".venv_t5_530 missing at {_VENV_T5_530_DIR}"
|
||||
)
|
||||
if _VENV_T5_530_DIR not in sys.path:
|
||||
sys.path.insert(0, _VENV_T5_530_DIR)
|
||||
logger.info("Activated transformers 5.3.0 from %s", _VENV_T5_530_DIR)
|
||||
_pp = os.environ.get("PYTHONPATH", "")
|
||||
os.environ["PYTHONPATH"] = _VENV_T5_530_DIR + (os.pathsep + _pp if _pp else "")
|
||||
else:
|
||||
logger.info("Using default transformers (4.57.x) for %s", model_name)
|
||||
|
||||
|
||||
def _resolve_base_model(model_name: str) -> str:
|
||||
|
|
@ -192,18 +259,103 @@ def _check_tokenizer_config_needs_v5(model_name: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def needs_transformers_5(model_name: str) -> bool:
|
||||
"""Return True if *model_name* belongs to an architecture that requires
|
||||
``transformers>=5.3.0``.
|
||||
def _check_config_needs_550(model_name: str) -> bool:
|
||||
"""Check ``config.json`` for architectures or model_type that require
|
||||
transformers 5.5.0 (e.g. Gemma 4).
|
||||
|
||||
First checks the hardcoded substring list for known models, then
|
||||
dynamically fetches ``tokenizer_config.json`` from HuggingFace to check
|
||||
if the tokenizer_class (e.g. ``TokenizersBackend``) requires v5.
|
||||
Checks locally first, then falls back to fetching from HuggingFace.
|
||||
Results are cached in ``_config_needs_550_cache``.
|
||||
Returns False on any error (fail-open to lower tier).
|
||||
"""
|
||||
if model_name in _config_needs_550_cache:
|
||||
return _config_needs_550_cache[model_name]
|
||||
|
||||
def _check_cfg(cfg: dict) -> bool:
|
||||
archs = cfg.get("architectures", [])
|
||||
if any(a in _TRANSFORMERS_550_ARCHITECTURES for a in archs):
|
||||
return True
|
||||
if cfg.get("model_type") in _TRANSFORMERS_550_MODEL_TYPES:
|
||||
return True
|
||||
return False
|
||||
|
||||
# --- Check local config.json first ------------------------------------
|
||||
local_path = Path(model_name)
|
||||
local_cfg = local_path / "config.json"
|
||||
if local_cfg.is_file():
|
||||
try:
|
||||
with open(local_cfg) as f:
|
||||
cfg = json.load(f)
|
||||
result = _check_cfg(cfg)
|
||||
if result:
|
||||
logger.info(
|
||||
"Local config.json check: %s needs transformers 5.5.0 "
|
||||
"(architectures=%s, model_type=%s)",
|
||||
model_name,
|
||||
cfg.get("architectures", []),
|
||||
cfg.get("model_type"),
|
||||
)
|
||||
_config_needs_550_cache[model_name] = result
|
||||
return result
|
||||
except Exception as exc:
|
||||
logger.debug("Could not read %s: %s", local_cfg, exc)
|
||||
|
||||
# --- Fall back to fetching from HuggingFace ---------------------------
|
||||
import urllib.request
|
||||
|
||||
url = f"https://huggingface.co/{model_name}/raw/main/config.json"
|
||||
try:
|
||||
req = urllib.request.Request(url, headers = {"User-Agent": "unsloth-studio"})
|
||||
with urllib.request.urlopen(req, timeout = 10) as resp:
|
||||
cfg = json.loads(resp.read().decode())
|
||||
result = _check_cfg(cfg)
|
||||
if result:
|
||||
logger.info(
|
||||
"Dynamic config.json check: %s needs transformers 5.5.0 "
|
||||
"(architectures=%s, model_type=%s)",
|
||||
model_name,
|
||||
cfg.get("architectures", []),
|
||||
cfg.get("model_type"),
|
||||
)
|
||||
_config_needs_550_cache[model_name] = result
|
||||
return result
|
||||
except Exception as exc:
|
||||
logger.debug("Could not fetch config.json for '%s': %s", model_name, exc)
|
||||
_config_needs_550_cache[model_name] = False
|
||||
return False
|
||||
|
||||
|
||||
def get_transformers_tier(model_name: str) -> str:
|
||||
"""Return the transformers tier required for *model_name*.
|
||||
|
||||
Returns ``"550"`` for models needing transformers 5.5.0 (e.g. Gemma 4),
|
||||
``"530"`` for models needing transformers 5.3.0 (e.g. Ministral-3, Qwen3 MoE),
|
||||
or ``"default"`` for everything else (4.57.x).
|
||||
|
||||
The 5.5.0 check runs first, then 5.3.0.
|
||||
"""
|
||||
lowered = model_name.lower()
|
||||
|
||||
# --- Fast substring checks (no I/O) ------------------------------------
|
||||
if any(sub in lowered for sub in TRANSFORMERS_550_MODEL_SUBSTRINGS):
|
||||
return "550"
|
||||
if any(sub in lowered for sub in TRANSFORMERS_5_MODEL_SUBSTRINGS):
|
||||
return True
|
||||
return _check_tokenizer_config_needs_v5(model_name)
|
||||
return "530"
|
||||
|
||||
# --- Slow config fallbacks (local file first, then network) -----------
|
||||
if _check_config_needs_550(model_name):
|
||||
return "550"
|
||||
if _check_tokenizer_config_needs_v5(model_name):
|
||||
return "530"
|
||||
|
||||
return "default"
|
||||
|
||||
|
||||
def needs_transformers_5(model_name: str) -> bool:
|
||||
"""Return True if *model_name* requires any transformers 5.x version.
|
||||
|
||||
Convenience wrapper around :func:`get_transformers_tier`.
|
||||
"""
|
||||
return get_transformers_tier(model_name) != "default"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -258,27 +410,36 @@ def _purge_modules() -> int:
|
|||
return len(to_remove)
|
||||
|
||||
|
||||
_VENV_T5_PACKAGES = (
|
||||
f"transformers=={TRANSFORMERS_5_VERSION}",
|
||||
_VENV_T5_530_PACKAGES = (
|
||||
f"transformers=={TRANSFORMERS_530_VERSION}",
|
||||
"huggingface_hub==1.8.0",
|
||||
"hf_xet==1.4.2",
|
||||
"tiktoken",
|
||||
)
|
||||
|
||||
_VENV_T5_550_PACKAGES = (
|
||||
f"transformers=={TRANSFORMERS_550_VERSION}",
|
||||
"huggingface_hub==1.8.0",
|
||||
"hf_xet==1.4.2",
|
||||
"tiktoken",
|
||||
)
|
||||
|
||||
def _venv_t5_is_valid() -> bool:
|
||||
"""Return True if .venv_t5/ has all required packages at the correct versions."""
|
||||
if not os.path.isdir(_VENV_T5_DIR) or not os.listdir(_VENV_T5_DIR):
|
||||
# Backwards-compat alias
|
||||
_VENV_T5_PACKAGES = _VENV_T5_550_PACKAGES
|
||||
|
||||
|
||||
def _venv_dir_is_valid(venv_dir: str, packages: tuple[str, ...]) -> bool:
|
||||
"""Return True if *venv_dir* has all *packages* at the correct versions."""
|
||||
if not os.path.isdir(venv_dir) or not os.listdir(venv_dir):
|
||||
return False
|
||||
# Check that the key package directories exist AND match the required version
|
||||
for pkg_spec in _VENV_T5_PACKAGES:
|
||||
for pkg_spec in packages:
|
||||
parts = pkg_spec.split("==")
|
||||
pkg_name = parts[0]
|
||||
pkg_version = parts[1] if len(parts) > 1 else None
|
||||
pkg_name_norm = pkg_name.replace("-", "_")
|
||||
# Check directory exists
|
||||
if not any(
|
||||
(Path(_VENV_T5_DIR) / d).is_dir()
|
||||
(Path(venv_dir) / d).is_dir()
|
||||
for d in (pkg_name_norm, pkg_name_norm.replace("_", "-"))
|
||||
):
|
||||
return False
|
||||
|
|
@ -287,7 +448,7 @@ def _venv_t5_is_valid() -> bool:
|
|||
continue
|
||||
# Check version via .dist-info metadata
|
||||
dist_info_found = False
|
||||
for di in Path(_VENV_T5_DIR).glob(f"{pkg_name_norm}-*.dist-info"):
|
||||
for di in Path(venv_dir).glob(f"{pkg_name_norm}-*.dist-info"):
|
||||
metadata = di / "METADATA"
|
||||
if not metadata.is_file():
|
||||
continue
|
||||
|
|
@ -296,7 +457,8 @@ def _venv_t5_is_valid() -> bool:
|
|||
installed_ver = line.split(":", 1)[1].strip()
|
||||
if installed_ver != pkg_version:
|
||||
logger.info(
|
||||
".venv_t5 has %s==%s but need %s",
|
||||
"%s has %s==%s but need %s",
|
||||
venv_dir,
|
||||
pkg_name,
|
||||
installed_ver,
|
||||
pkg_version,
|
||||
|
|
@ -311,8 +473,13 @@ def _venv_t5_is_valid() -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def _install_to_venv_t5(pkg: str) -> bool:
|
||||
"""Install a single package into .venv_t5/, preferring uv then pip."""
|
||||
def _venv_t5_is_valid() -> bool:
|
||||
"""Backwards-compat: check the 5.5.0 venv."""
|
||||
return _venv_dir_is_valid(_VENV_T5_550_DIR, _VENV_T5_550_PACKAGES)
|
||||
|
||||
|
||||
def _install_to_dir(pkg: str, target_dir: str) -> bool:
|
||||
"""Install a single package into *target_dir*, preferring uv then pip."""
|
||||
# Try uv first (faster) if already on PATH -- do NOT install uv at runtime
|
||||
if shutil.which("uv"):
|
||||
result = subprocess.run(
|
||||
|
|
@ -323,7 +490,7 @@ def _install_to_venv_t5(pkg: str) -> bool:
|
|||
"--python",
|
||||
sys.executable,
|
||||
"--target",
|
||||
_VENV_T5_DIR,
|
||||
target_dir,
|
||||
"--no-deps",
|
||||
"--upgrade",
|
||||
pkg,
|
||||
|
|
@ -344,7 +511,7 @@ def _install_to_venv_t5(pkg: str) -> bool:
|
|||
"pip",
|
||||
"install",
|
||||
"--target",
|
||||
_VENV_T5_DIR,
|
||||
target_dir,
|
||||
"--no-deps",
|
||||
"--upgrade",
|
||||
pkg,
|
||||
|
|
@ -359,47 +526,62 @@ def _install_to_venv_t5(pkg: str) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def _ensure_venv_t5_exists() -> bool:
|
||||
"""Ensure .venv_t5/ exists with all required packages. Install if missing."""
|
||||
if _venv_t5_is_valid():
|
||||
def _ensure_venv_dir(venv_dir: str, packages: tuple[str, ...], label: str) -> bool:
|
||||
"""Ensure *venv_dir* exists with all *packages*. Install if missing."""
|
||||
if _venv_dir_is_valid(venv_dir, packages):
|
||||
return True
|
||||
|
||||
logger.warning(
|
||||
".venv_t5 not found or incomplete at %s -- installing at runtime", _VENV_T5_DIR
|
||||
"%s not found or incomplete at %s -- installing at runtime", label, venv_dir
|
||||
)
|
||||
shutil.rmtree(_VENV_T5_DIR, ignore_errors = True)
|
||||
os.makedirs(_VENV_T5_DIR, exist_ok = True)
|
||||
for pkg in _VENV_T5_PACKAGES:
|
||||
if not _install_to_venv_t5(pkg):
|
||||
shutil.rmtree(venv_dir, ignore_errors = True)
|
||||
os.makedirs(venv_dir, exist_ok = True)
|
||||
for pkg in packages:
|
||||
if not _install_to_dir(pkg, venv_dir):
|
||||
return False
|
||||
logger.info("Installed transformers 5.x to %s", _VENV_T5_DIR)
|
||||
logger.info("Installed %s to %s", label, venv_dir)
|
||||
return True
|
||||
|
||||
|
||||
def _activate_5x() -> None:
|
||||
"""Prepend .venv_t5/ to sys.path, purge stale modules, reimport."""
|
||||
if not _ensure_venv_t5_exists():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}"
|
||||
)
|
||||
def _ensure_venv_t5_530_exists() -> bool:
|
||||
"""Ensure .venv_t5_530/ exists with transformers 5.3.0."""
|
||||
return _ensure_venv_dir(
|
||||
_VENV_T5_530_DIR, _VENV_T5_530_PACKAGES, "transformers 5.3.0"
|
||||
)
|
||||
|
||||
if _VENV_T5_DIR not in sys.path:
|
||||
sys.path.insert(0, _VENV_T5_DIR)
|
||||
logger.info("Prepended %s to sys.path", _VENV_T5_DIR)
|
||||
|
||||
def _ensure_venv_t5_550_exists() -> bool:
|
||||
"""Ensure .venv_t5_550/ exists with transformers 5.5.0."""
|
||||
return _ensure_venv_dir(
|
||||
_VENV_T5_550_DIR, _VENV_T5_550_PACKAGES, "transformers 5.5.0"
|
||||
)
|
||||
|
||||
|
||||
def _ensure_venv_t5_exists() -> bool:
|
||||
"""Backwards-compat: ensure the 5.5.0 venv exists."""
|
||||
return _ensure_venv_t5_550_exists()
|
||||
|
||||
|
||||
def _activate_venv(venv_dir: str, label: str) -> None:
|
||||
"""Prepend *venv_dir* to sys.path, purge stale modules, reimport."""
|
||||
if venv_dir not in sys.path:
|
||||
sys.path.insert(0, venv_dir)
|
||||
logger.info("Prepended %s to sys.path", venv_dir)
|
||||
|
||||
count = _purge_modules()
|
||||
logger.info("Purged %d cached modules", count)
|
||||
|
||||
import transformers
|
||||
|
||||
logger.info("Loaded transformers %s", transformers.__version__)
|
||||
logger.info("Loaded transformers %s (%s)", transformers.__version__, label)
|
||||
|
||||
|
||||
def _deactivate_5x() -> None:
|
||||
"""Remove .venv_t5/ from sys.path, purge stale modules, reimport."""
|
||||
while _VENV_T5_DIR in sys.path:
|
||||
sys.path.remove(_VENV_T5_DIR)
|
||||
logger.info("Removed %s from sys.path", _VENV_T5_DIR)
|
||||
"""Remove all .venv_t5_*/ dirs from sys.path, purge stale modules, reimport."""
|
||||
for d in (_VENV_T5_530_DIR, _VENV_T5_550_DIR):
|
||||
while d in sys.path:
|
||||
sys.path.remove(d)
|
||||
logger.info("Removed venv_t5 dirs from sys.path")
|
||||
|
||||
count = _purge_modules()
|
||||
logger.info("Purged %d cached modules", count)
|
||||
|
|
@ -412,9 +594,10 @@ def _deactivate_5x() -> None:
|
|||
def ensure_transformers_version(model_name: str) -> None:
|
||||
"""Ensure the correct ``transformers`` version is active for *model_name*.
|
||||
|
||||
Uses sys.path with .venv_t5/ (pre-installed by setup.sh):
|
||||
• Need 5.x → prepend .venv_t5/ to sys.path, purge modules.
|
||||
• Need 4.x → remove .venv_t5/ from sys.path, purge modules.
|
||||
Uses sys.path with .venv_t5_530/ or .venv_t5_550/ (pre-installed by setup.sh):
|
||||
• Need 5.5.0 → prepend .venv_t5_550/ to sys.path, purge modules.
|
||||
• Need 5.3.0 → prepend .venv_t5_530/ to sys.path, purge modules.
|
||||
• Need 4.x → remove all .venv_t5_*/ from sys.path, purge modules.
|
||||
|
||||
For LoRA adapters with custom names, the base model is resolved from
|
||||
``adapter_config.json`` before checking.
|
||||
|
|
@ -424,8 +607,21 @@ def ensure_transformers_version(model_name: str) -> None:
|
|||
"""
|
||||
# Resolve LoRA adapters to their base model for accurate detection
|
||||
resolved = _resolve_base_model(model_name)
|
||||
want_5 = needs_transformers_5(resolved)
|
||||
target_version = TRANSFORMERS_5_VERSION if want_5 else TRANSFORMERS_DEFAULT_VERSION
|
||||
tier = get_transformers_tier(resolved)
|
||||
|
||||
if tier == "550":
|
||||
target_version = TRANSFORMERS_550_VERSION
|
||||
venv_dir = _VENV_T5_550_DIR
|
||||
ensure_fn = _ensure_venv_t5_550_exists
|
||||
elif tier == "530":
|
||||
target_version = TRANSFORMERS_530_VERSION
|
||||
venv_dir = _VENV_T5_530_DIR
|
||||
ensure_fn = _ensure_venv_t5_530_exists
|
||||
else:
|
||||
target_version = TRANSFORMERS_DEFAULT_VERSION
|
||||
venv_dir = None
|
||||
ensure_fn = None
|
||||
|
||||
target_major = int(target_version.split(".")[0])
|
||||
|
||||
# Check what's actually loaded in memory
|
||||
|
|
@ -441,8 +637,17 @@ def ensure_transformers_version(model_name: str) -> None:
|
|||
|
||||
# --- Already correct? ---------------------------------------------------
|
||||
if in_memory is not None:
|
||||
if in_memory == target_version:
|
||||
logger.info(
|
||||
"transformers %s already loaded — correct for '%s'",
|
||||
in_memory,
|
||||
model_name,
|
||||
)
|
||||
return
|
||||
# Different 5.x → need to switch (e.g. 5.3.0 loaded but need 5.5.0)
|
||||
in_memory_major = int(in_memory.split(".")[0])
|
||||
if in_memory_major == target_major:
|
||||
if in_memory_major == target_major and venv_dir is None:
|
||||
# Both are default (4.x) — close enough
|
||||
logger.info(
|
||||
"transformers %s already loaded — correct for '%s'",
|
||||
in_memory,
|
||||
|
|
@ -451,9 +656,16 @@ def ensure_transformers_version(model_name: str) -> None:
|
|||
return
|
||||
|
||||
# --- Switch version -----------------------------------------------------
|
||||
if want_5:
|
||||
logger.info("Activating transformers %s via .venv_t5…", TRANSFORMERS_5_VERSION)
|
||||
_activate_5x()
|
||||
if venv_dir is not None:
|
||||
# First remove any other 5.x venv from sys.path
|
||||
_deactivate_5x()
|
||||
if not ensure_fn():
|
||||
raise RuntimeError(
|
||||
f"Cannot activate transformers {target_version}: "
|
||||
f"venv missing at {venv_dir}"
|
||||
)
|
||||
logger.info("Activating transformers %s…", target_version)
|
||||
_activate_venv(venv_dir, f"transformers {target_version}")
|
||||
else:
|
||||
logger.info(
|
||||
"Reverting to default transformers %s…", TRANSFORMERS_DEFAULT_VERSION
|
||||
|
|
|
|||
135
studio/setup.ps1
135
studio/setup.ps1
|
|
@ -1579,55 +1579,104 @@ if ($stackExit -ne 0) {
|
|||
exit 1
|
||||
}
|
||||
|
||||
# ── Pre-install transformers 5.x into .venv_t5/ ──
|
||||
# Models like GLM-4.7-Flash need transformers>=5.3.0. Instead of pip-installing
|
||||
# at runtime (slow, ~10-15s), we pre-install into a separate directory.
|
||||
# The training subprocess just prepends .venv_t5/ to sys.path -- instant switch.
|
||||
Write-Host ""
|
||||
substep "pre-installing transformers 5.x for newer model support..."
|
||||
$VenvT5Dir = Join-Path $env:USERPROFILE ".unsloth\studio\.venv_t5"
|
||||
if (Test-Path $VenvT5Dir) { Remove-Item -Recurse -Force $VenvT5Dir }
|
||||
New-Item -ItemType Directory -Path $VenvT5Dir -Force | Out-Null
|
||||
$prevEAP_t5 = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
foreach ($pkg in @("transformers==5.5.0", "huggingface_hub==1.8.0", "hf_xet==1.4.2")) {
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --target $VenvT5Dir --no-deps $pkg
|
||||
$t5PkgExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install --target $VenvT5Dir --no-deps $pkg | Out-String
|
||||
$t5PkgExit = $LASTEXITCODE
|
||||
}
|
||||
if ($t5PkgExit -ne 0) {
|
||||
Write-Host "[FAIL] Could not install $pkg into .venv_t5/" -ForegroundColor Red
|
||||
Write-Host $output -ForegroundColor Red
|
||||
$ErrorActionPreference = $prevEAP_t5
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
# tiktoken is needed by Qwen-family tokenizers -- install with deps since
|
||||
# regex/requests may be missing on Windows
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --target $VenvT5Dir tiktoken
|
||||
$tiktokenInstallExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install --target $VenvT5Dir tiktoken | Out-String
|
||||
$tiktokenInstallExit = $LASTEXITCODE
|
||||
}
|
||||
if ($tiktokenInstallExit -ne 0) {
|
||||
substep "Could not install tiktoken into .venv_t5/ -- Qwen tokenizers may fail" "Yellow"
|
||||
}
|
||||
$ErrorActionPreference = $prevEAP_t5
|
||||
step "transformers" "5.x pre-installed"
|
||||
|
||||
} else {
|
||||
step "python" "dependencies up to date"
|
||||
# Restore ErrorActionPreference (was lowered for pip/python section)
|
||||
$ErrorActionPreference = $prevEAP
|
||||
}
|
||||
|
||||
# ── Pre-install transformers 5.x into .venv_t5_530/ and .venv_t5_550/ ──
|
||||
# Runs outside the deps fast-path gate so that upgrades from the legacy
|
||||
# single .venv_t5 are always migrated to the tiered layout.
|
||||
$VenvT5_530Dir = Join-Path $env:USERPROFILE ".unsloth\studio\.venv_t5_530"
|
||||
$VenvT5_550Dir = Join-Path $env:USERPROFILE ".unsloth\studio\.venv_t5_550"
|
||||
$VenvT5Legacy = Join-Path $env:USERPROFILE ".unsloth\studio\.venv_t5"
|
||||
|
||||
$_NeedT5Install = $false
|
||||
if (Test-Path $VenvT5Legacy) {
|
||||
Remove-Item -Recurse -Force $VenvT5Legacy
|
||||
$_NeedT5Install = $true
|
||||
}
|
||||
if (-not (Test-Path $VenvT5_530Dir)) { $_NeedT5Install = $true }
|
||||
if (-not (Test-Path $VenvT5_550Dir)) { $_NeedT5Install = $true }
|
||||
# Also reinstall when python deps were updated
|
||||
if (-not $SkipPythonDeps) { $_NeedT5Install = $true }
|
||||
|
||||
if ($_NeedT5Install) {
|
||||
Write-Host ""
|
||||
|
||||
$prevEAP_t5 = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
# --- .venv_t5_530 (transformers 5.3.0) ---
|
||||
substep "pre-installing transformers 5.3.0 for newer model support..."
|
||||
if (Test-Path $VenvT5_530Dir) { Remove-Item -Recurse -Force $VenvT5_530Dir }
|
||||
New-Item -ItemType Directory -Path $VenvT5_530Dir -Force | Out-Null
|
||||
foreach ($pkg in @("transformers==5.3.0", "huggingface_hub==1.8.0", "hf_xet==1.4.2")) {
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --target $VenvT5_530Dir --no-deps $pkg
|
||||
$t5PkgExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install --target $VenvT5_530Dir --no-deps $pkg | Out-String
|
||||
$t5PkgExit = $LASTEXITCODE
|
||||
}
|
||||
if ($t5PkgExit -ne 0) {
|
||||
Write-Host "[FAIL] Could not install $pkg into .venv_t5_530/" -ForegroundColor Red
|
||||
Write-Host $output -ForegroundColor Red
|
||||
$ErrorActionPreference = $prevEAP_t5
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --target $VenvT5_530Dir tiktoken
|
||||
$tiktokenInstallExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install --target $VenvT5_530Dir tiktoken | Out-String
|
||||
$tiktokenInstallExit = $LASTEXITCODE
|
||||
}
|
||||
if ($tiktokenInstallExit -ne 0) {
|
||||
substep "Could not install tiktoken into .venv_t5_530/ -- Qwen tokenizers may fail" "Yellow"
|
||||
}
|
||||
step "transformers" "5.3.0 pre-installed"
|
||||
|
||||
# --- .venv_t5_550 (transformers 5.5.0) ---
|
||||
substep "pre-installing transformers 5.5.0 for Gemma 4 support..."
|
||||
if (Test-Path $VenvT5_550Dir) { Remove-Item -Recurse -Force $VenvT5_550Dir }
|
||||
New-Item -ItemType Directory -Path $VenvT5_550Dir -Force | Out-Null
|
||||
foreach ($pkg in @("transformers==5.5.0", "huggingface_hub==1.8.0", "hf_xet==1.4.2")) {
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --target $VenvT5_550Dir --no-deps $pkg
|
||||
$t5PkgExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install --target $VenvT5_550Dir --no-deps $pkg | Out-String
|
||||
$t5PkgExit = $LASTEXITCODE
|
||||
}
|
||||
if ($t5PkgExit -ne 0) {
|
||||
Write-Host "[FAIL] Could not install $pkg into .venv_t5_550/" -ForegroundColor Red
|
||||
Write-Host $output -ForegroundColor Red
|
||||
$ErrorActionPreference = $prevEAP_t5
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --target $VenvT5_550Dir tiktoken
|
||||
$tiktokenInstallExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install --target $VenvT5_550Dir tiktoken | Out-String
|
||||
$tiktokenInstallExit = $LASTEXITCODE
|
||||
}
|
||||
if ($tiktokenInstallExit -ne 0) {
|
||||
substep "Could not install tiktoken into .venv_t5_550/ -- Qwen tokenizers may fail" "Yellow"
|
||||
}
|
||||
$ErrorActionPreference = $prevEAP_t5
|
||||
step "transformers" "5.5.0 pre-installed"
|
||||
|
||||
} # end $_NeedT5Install
|
||||
|
||||
# ==========================================================================
|
||||
# PHASE 3.4: Prefer prebuilt llama.cpp bundles before source build
|
||||
# ==========================================================================
|
||||
|
|
|
|||
|
|
@ -394,11 +394,14 @@ fi
|
|||
# ── Python venv + deps ──
|
||||
STUDIO_HOME="$HOME/.unsloth/studio"
|
||||
VENV_DIR="$STUDIO_HOME/unsloth_studio"
|
||||
VENV_T5_DIR="$STUDIO_HOME/.venv_t5"
|
||||
VENV_T5_530_DIR="$STUDIO_HOME/.venv_t5_530"
|
||||
VENV_T5_550_DIR="$STUDIO_HOME/.venv_t5_550"
|
||||
|
||||
[ -d "$REPO_ROOT/.venv" ] && rm -rf "$REPO_ROOT/.venv"
|
||||
[ -d "$REPO_ROOT/.venv_overlay" ] && rm -rf "$REPO_ROOT/.venv_overlay"
|
||||
[ -d "$REPO_ROOT/.venv_t5" ] && rm -rf "$REPO_ROOT/.venv_t5"
|
||||
[ -d "$REPO_ROOT/.venv_t5_530" ] && rm -rf "$REPO_ROOT/.venv_t5_530"
|
||||
[ -d "$REPO_ROOT/.venv_t5_550" ] && rm -rf "$REPO_ROOT/.venv_t5_550"
|
||||
# Note: do NOT delete $STUDIO_HOME/.venv here — install.sh handles migration
|
||||
|
||||
_COLAB_NO_VENV=false
|
||||
|
|
@ -501,21 +504,47 @@ fi
|
|||
|
||||
if [ "$_SKIP_PYTHON_DEPS" = false ]; then
|
||||
install_python_stack
|
||||
|
||||
# ── 6b. Pre-install transformers 5.x into .venv_t5/ ──
|
||||
# Models like GLM-4.7-Flash need transformers>=5.3.0. Instead of pip-installing
|
||||
# at runtime (slow, ~10-15s), we pre-install into a separate directory.
|
||||
# The training subprocess just prepends .venv_t5/ to sys.path -- instant switch.
|
||||
mkdir -p "$VENV_T5_DIR"
|
||||
run_quiet "install transformers 5.x" fast_install --target "$VENV_T5_DIR" --no-deps "transformers==5.5.0"
|
||||
run_quiet "install huggingface_hub for t5" fast_install --target "$VENV_T5_DIR" --no-deps "huggingface_hub==1.8.0"
|
||||
run_quiet "install hf_xet for t5" fast_install --target "$VENV_T5_DIR" --no-deps "hf_xet==1.4.2"
|
||||
run_quiet "install tiktoken for t5" fast_install --target "$VENV_T5_DIR" "tiktoken"
|
||||
step "transformers" "5.x pre-installed"
|
||||
else
|
||||
step "python" "dependencies up to date"
|
||||
verbose_substep "python deps check: installed=$_PKG_NAME@${INSTALLED_VER:-unknown} latest=${LATEST_VER:-unknown}"
|
||||
fi
|
||||
|
||||
# ── 6b. Pre-install transformers 5.x into .venv_t5_530/ and .venv_t5_550/ ──
|
||||
# Models like GLM-4.7-Flash, Qwen3 MoE need transformers>=5.3.0.
|
||||
# Gemma 4 models need transformers>=5.5.0.
|
||||
# Pre-install into separate directories to avoid runtime pip overhead.
|
||||
# The training subprocess prepends the appropriate dir to sys.path.
|
||||
#
|
||||
# Runs outside the _SKIP_PYTHON_DEPS gate so that upgrades from legacy
|
||||
# single .venv_t5 are always migrated to the tiered layout.
|
||||
_NEED_T5_INSTALL=false
|
||||
if [ -d "$STUDIO_HOME/.venv_t5" ]; then
|
||||
# Legacy layout — migrate
|
||||
rm -rf "$STUDIO_HOME/.venv_t5"
|
||||
_NEED_T5_INSTALL=true
|
||||
fi
|
||||
[ ! -d "$VENV_T5_530_DIR" ] && _NEED_T5_INSTALL=true
|
||||
[ ! -d "$VENV_T5_550_DIR" ] && _NEED_T5_INSTALL=true
|
||||
# Also reinstall when python deps were updated (packages may need rebuild)
|
||||
[ "$_SKIP_PYTHON_DEPS" = false ] && _NEED_T5_INSTALL=true
|
||||
|
||||
if [ "$_NEED_T5_INSTALL" = true ]; then
|
||||
[ -d "$VENV_T5_530_DIR" ] && rm -rf "$VENV_T5_530_DIR"
|
||||
mkdir -p "$VENV_T5_530_DIR"
|
||||
run_quiet "install transformers 5.3.0" fast_install --target "$VENV_T5_530_DIR" --no-deps "transformers==5.3.0"
|
||||
run_quiet "install huggingface_hub for t5_530" fast_install --target "$VENV_T5_530_DIR" --no-deps "huggingface_hub==1.8.0"
|
||||
run_quiet "install hf_xet for t5_530" fast_install --target "$VENV_T5_530_DIR" --no-deps "hf_xet==1.4.2"
|
||||
run_quiet "install tiktoken for t5_530" fast_install --target "$VENV_T5_530_DIR" "tiktoken"
|
||||
step "transformers" "5.3.0 pre-installed"
|
||||
|
||||
[ -d "$VENV_T5_550_DIR" ] && rm -rf "$VENV_T5_550_DIR"
|
||||
mkdir -p "$VENV_T5_550_DIR"
|
||||
run_quiet "install transformers 5.5.0" fast_install --target "$VENV_T5_550_DIR" --no-deps "transformers==5.5.0"
|
||||
run_quiet "install huggingface_hub for t5_550" fast_install --target "$VENV_T5_550_DIR" --no-deps "huggingface_hub==1.8.0"
|
||||
run_quiet "install hf_xet for t5_550" fast_install --target "$VENV_T5_550_DIR" --no-deps "hf_xet==1.4.2"
|
||||
run_quiet "install tiktoken for t5_550" fast_install --target "$VENV_T5_550_DIR" "tiktoken"
|
||||
step "transformers" "5.5.0 pre-installed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 7. Prefer prebuilt llama.cpp bundles before any source build path ──
|
||||
|
|
|
|||
Loading…
Reference in a new issue