Merge branch 'main' into studio-composer

This commit is contained in:
Michael Han 2026-05-30 05:11:22 -07:00 committed by GitHub
commit a879ca1d82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1179 additions and 364 deletions

View file

@ -0,0 +1,142 @@
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Shared torchao Windows-ROCm import stub.
torchao (pulled in by transformers.quantizers) imports
torch.distributed._functional_collectives at module level, which imports
distributed_c10d.py unconditionally that file crashes on Windows ROCm because
torch._C._distributed_c10d (the RCCL backend) is absent.
torch/distributed/__init__.py itself is guarded by `if is_available()` so
`import torch.distributed` alone is safe; the crash only comes via torchao's
import chain. Stubbing torchao short-circuits it entirely.
_StubSubpackageFinder handles any depth of torchao.xxx.yyy imports.
This logic used to be duplicated inline inside run_export_process() and
run_training_process(); it now lives here so both worker subprocesses call the
single `install_torchao_windows_rocm_stub()` entrypoint before importing
transformers / unsloth_zoo.
"""
from __future__ import annotations
import sys
import types
import importlib.abc
import importlib.machinery
_STUB_SENTINEL = object()
# Metaclass for stub types so that isinstance(x, StubClass) returns False
# instead of raising TypeError ("arg 2 must be a type").
# peft/tuners/lora/torchao.py does:
# from torchao.dtypes import AffineQuantizedTensor, LinearActivationQuantizedTensor
# isinstance(weight, (AffineQuantizedTensor, LinearActivationQuantizedTensor))
# If those names resolve to stub modules rather than types, isinstance() raises.
class _StubTypeMeta(type):
def __instancecheck__(cls, instance):
return False
def __subclasscheck__(cls, subclass):
return False
def __getattr__(cls, attr):
if attr.startswith("__"):
raise AttributeError(attr)
child = _StubTypeMeta(attr, (), {})
setattr(cls, attr, child)
return child
def __call__(cls, *args, **kwargs):
return None
def _make_stub_type(name):
"""Stub class: accepted by isinstance() (always False), supports attr access."""
return _StubTypeMeta(name, (), {})
def _make_mod_stub(mod_name):
m = types.ModuleType(mod_name)
m.__path__ = []
m.__package__ = mod_name
m._unsloth_stub = _STUB_SENTINEL
m.__spec__ = importlib.machinery.ModuleSpec(mod_name, loader = None, is_package = True)
def _ga(attr, _m = m, _n = mod_name):
if attr.startswith("__"):
raise AttributeError(attr)
# Return a stub CLASS (not a module) so that isinstance(x, attr)
# works and returns False instead of raising TypeError.
child = _make_stub_type(f"{_n}.{attr}")
setattr(_m, attr, child)
return child
m.__getattr__ = _ga
return m
class _StubSubpackageLoader(importlib.abc.Loader):
def __init__(self, mod_name):
self._mod_name = mod_name
def create_module(self, spec):
return _make_mod_stub(self._mod_name)
def exec_module(self, module):
pass
class _StubSubpackageFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target = None):
if "." not in fullname:
return None
parent = sys.modules.get(fullname.rsplit(".", 1)[0])
if parent is None:
return None
if getattr(parent, "_unsloth_stub", None) is not _STUB_SENTINEL:
return None
return importlib.machinery.ModuleSpec(
fullname, _StubSubpackageLoader(fullname), is_package = True
)
def install_torchao_windows_rocm_stub() -> None:
"""Pre-stub torchao on Windows ROCm so transformers/peft imports don't crash.
No-op on every other platform (Windows CUDA included there torchao is real
and shadowing it would break torchao-based quantization paths). Must run
before any import of transformers / unsloth_zoo. Safe to call once per worker
process.
"""
# Gate on the active torch runtime, not env-var presence -- HIP_PATH /
# ROCM_PATH stay set after a user installs the HIP SDK and reverts to a
# CUDA torch wheel. AMD SDK / Radeon ROCm wheels may not set torch.version.hip
# but still encode "rocm" in torch.__version__, so accept either.
_is_win32_rocm = False
if sys.platform == "win32":
try:
import torch as _torch_probe
_is_win32_rocm = bool(
getattr(getattr(_torch_probe, "version", None), "hip", None)
or "rocm" in getattr(_torch_probe, "__version__", "").lower()
)
del _torch_probe
except Exception:
pass
if _is_win32_rocm:
# Register the finder only on Windows ROCm -- on other platforms there
# are no stub modules seeded, so appending is a pure accumulation.
sys.meta_path.append(_StubSubpackageFinder())
# Seed torchao top-level + key submodules; the finder handles the rest.
for _tao_name in (
"torchao",
"torchao.quantization",
"torchao.dtypes",
"torchao.float8",
"torchao.utils",
):
if _tao_name not in sys.modules:
sys.modules[_tao_name] = _make_mod_stub(_tao_name)

View file

@ -440,101 +440,13 @@ def run_export_process(
)
# ── 1c. Stub torchao on Windows ROCm ──
# torchao (pulled in by transformers.quantizers) imports
# torch.distributed._functional_collectives at module level, which imports
# distributed_c10d.py unconditionally — that file crashes on Windows ROCm
# because torch._C._distributed_c10d (the RCCL backend) is absent.
# Stubbing torchao short-circuits the crash entirely.
# Shared with the training worker; see core/_torchao_stub.py for the full
# rationale (torchao -> torch.distributed._functional_collectives crashes on
# Windows ROCm because the RCCL backend is absent). No-op off Windows ROCm.
# Must run before any import of transformers / unsloth_zoo.
import types as _types
import importlib.machinery as _ilm
import importlib.abc as _ilabc
from core._torchao_stub import install_torchao_windows_rocm_stub
_STUB_SENTINEL = object()
class _StubTypeMeta(type):
def __instancecheck__(cls, instance):
return False
def __subclasscheck__(cls, subclass):
return False
def __getattr__(cls, attr):
if attr.startswith("__"):
raise AttributeError(attr)
child = _StubTypeMeta(attr, (), {})
setattr(cls, attr, child)
return child
def __call__(cls, *args, **kwargs):
return None
def _make_stub_type(name):
return _StubTypeMeta(name, (), {})
def _make_mod_stub(mod_name):
m = _types.ModuleType(mod_name)
m.__path__ = []
m.__package__ = mod_name
m._unsloth_stub = _STUB_SENTINEL
m.__spec__ = _ilm.ModuleSpec(mod_name, loader = None, is_package = True)
def _ga(attr, _m = m, _n = mod_name):
if attr.startswith("__"):
raise AttributeError(attr)
child = _make_stub_type(f"{_n}.{attr}")
setattr(_m, attr, child)
return child
m.__getattr__ = _ga
return m
class _StubSubpackageLoader(_ilabc.Loader):
def __init__(self, mod_name):
self._mod_name = mod_name
def create_module(self, spec):
return _make_mod_stub(self._mod_name)
def exec_module(self, module):
pass
class _StubSubpackageFinder(_ilabc.MetaPathFinder):
def find_spec(self, fullname, path, target = None):
if "." not in fullname:
return None
parent = sys.modules.get(fullname.rsplit(".", 1)[0])
if parent is None:
return None
if getattr(parent, "_unsloth_stub", None) is not _STUB_SENTINEL:
return None
return _ilm.ModuleSpec(
fullname, _StubSubpackageLoader(fullname), is_package = True
)
_is_win32_rocm = False
if sys.platform == "win32":
try:
import torch as _torch_probe
_is_win32_rocm = bool(
getattr(getattr(_torch_probe, "version", None), "hip", None)
or "rocm" in getattr(_torch_probe, "__version__", "").lower()
)
del _torch_probe
except Exception:
pass
if _is_win32_rocm:
sys.meta_path.append(_StubSubpackageFinder())
for _tao_name in (
"torchao",
"torchao.quantization",
"torchao.dtypes",
"torchao.float8",
"torchao.utils",
):
if _tao_name not in sys.modules:
sys.modules[_tao_name] = _make_mod_stub(_tao_name)
install_torchao_windows_rocm_stub()
# ── 2. Import ML libraries (fresh in this clean process) ──
try:

View file

@ -17,6 +17,7 @@ import struct
import structlog
from loggers import get_logger
import shutil
import signal
import socket
import subprocess
import sys
@ -965,9 +966,6 @@ class LlamaCppBackend:
7. llama-server on PATH (system install)
8. ./bin/llama-server (legacy: extracted binary)
"""
import os
import sys
binary_name = "llama-server.exe" if sys.platform == "win32" else "llama-server"
# 1. Env var — direct path to binary
@ -1282,8 +1280,6 @@ class LlamaCppBackend:
Returns list of (gpu_index, free_mib) sorted by index. Empty
list if no supported GPU is reachable.
"""
import os
# ── NVIDIA via nvidia-smi ────────────────────────────────────
try:
result = subprocess.run(
@ -3928,10 +3924,6 @@ class LlamaCppBackend:
Falls back to pgrep + /proc/<pid>/exe on Linux when psutil is
not installed.
"""
import os
import signal
import sys
try:
# -- Build the ownership allowlist --------------------------------
# Two kinds of matches:

View file

@ -6,8 +6,10 @@ Unsloth Training Backend
Integrates Unsloth training capabilities with the FastAPI backend
"""
import gc
import os
import sys
import types
# Prevent tokenizer parallelism deadlocks when datasets uses multiprocessing fork
os.environ["TOKENIZERS_PARALLELISM"] = "false"
@ -420,8 +422,6 @@ class UnslothTrainer:
in sys.modules. When the next training run calls dataset.map(num_proc=N),
forked child processes inherit this stale state and deadlock.
"""
import sys as _sys
# Remove cloned audio repo paths from sys.path
base_dir = os.path.dirname(os.path.abspath(__file__))
audio_paths = [
@ -436,15 +436,15 @@ class UnslothTrainer:
removed_paths = []
for path in audio_paths:
if path in _sys.path:
_sys.path.remove(path)
if path in sys.path:
sys.path.remove(path)
removed_paths.append(path)
# Remove stale audio modules from sys.modules
prefixes = ("snac", "whisper", "sparktts", "outetts")
removed_modules = [key for key in _sys.modules if key.startswith(prefixes)]
removed_modules = [key for key in sys.modules if key.startswith(prefixes)]
for key in removed_modules:
del _sys.modules[key]
del sys.modules[key]
if removed_paths or removed_modules:
logger.info(
@ -541,10 +541,9 @@ class UnslothTrainer:
# clear_unsloth_compiled_cache() deletes the disk cache, but the flag
# prevents re-compilation — leaving missing cache files. Reloading
# restores original class definitions so Unsloth can re-compile cleanly.
import sys as _sys
import importlib
for _key, _mod in list(_sys.modules.items()):
for _key, _mod in list(sys.modules.items()):
if "transformers.models." in _key and ".modeling_" in _key:
if hasattr(_mod, "__UNSLOTH_PATCHED__"):
try:
@ -660,14 +659,22 @@ class UnslothTrainer:
f"Using device_map='{device_map}' ({get_visible_gpu_count()} GPU(s) visible)"
)
# On hardware without native bfloat16 support (e.g. RDNA2 / gfx103x),
# passing dtype=None lets unsloth auto-detect and incorrectly choose
# bf16, triggering an LLVM error at the first bf16 kernel dispatch.
# Explicitly pass float16 as the fallback so unsloth never reaches
# that path. Modern NVIDIA (Ampere+) and RDNA3+ return True here so
# they are unaffected — dtype stays None and unsloth picks bf16 as
# before.
_auto_dtype = None if is_bfloat16_supported() else torch.float16
# AMD ROCm hardware without native bfloat16 (e.g. RDNA2 / gfx103x)
# crashes with an LLVM error at the first bf16 kernel dispatch if
# dtype=None lets unsloth auto-pick bf16. Force float16 there so that
# path is never reached. NVIDIA keeps dtype=None so unsloth's own
# bf16/fp16/float32 auto-detection (including FORCE_FLOAT32 models) is
# honored -- older NVIDIA without bf16 (T4/V100) must NOT be coerced to
# float16 here, which the previous unconditional branch did wrongly.
# Derive ROCm inline (not hardware.IS_ROCM) because that flag is unset
# until detect_hardware() runs, which isn't guaranteed in this subprocess.
_is_rocm = (
bool(getattr(torch.version, "hip", None))
or "rocm" in torch.__version__.lower()
)
_auto_dtype = (
torch.float16 if (_is_rocm and not is_bfloat16_supported()) else None
)
# Branch based on model type
if self._audio_type == "csm":
@ -1200,7 +1207,6 @@ class UnslothTrainer:
We patch at both instance AND class level for maximum reliability,
and strip non-TransformersKwargs params that Unsloth/PEFT inject.
"""
import types
import torch
import torch.nn as nn
from transformers.models.csm.modeling_csm import (
@ -1742,7 +1748,6 @@ class UnslothTrainer:
logger.info("Freeing SNAC codec model from GPU...\n")
snac_model.to("cpu")
del snac_model
import gc
gc.collect()
torch.cuda.empty_cache()
@ -1766,13 +1771,10 @@ class UnslothTrainer:
Mirrors Spark_TTS_(0_5B).ipynb: encode audio with BiCodec (semantic + global tokens),
format as special-token text strings for SFTTrainer with dataset_text_field="text".
"""
import sys
import torch
import numpy as np
import torchaudio.transforms as T
import subprocess
device = "cuda" if torch.cuda.is_available() else "cpu"
# The sparktts Python package lives in the SparkAudio/Spark-TTS GitHub repo,
@ -1972,7 +1974,6 @@ class UnslothTrainer:
audio_tokenizer.model.cpu()
audio_tokenizer.feature_extractor.cpu()
del audio_tokenizer
import gc
gc.collect()
torch.cuda.empty_cache()
@ -2001,7 +2002,6 @@ class UnslothTrainer:
OuteTTS AudioProcessor for speaker representations, PromptProcessor for
training prompts. Outputs text strings for SFTTrainer with dataset_text_field="text".
"""
import sys
import io
import tempfile
import torch
@ -2185,7 +2185,6 @@ class UnslothTrainer:
del whisper_model
del audio_processor
del prompt_processor
import gc
gc.collect()
torch.cuda.empty_cache()

View file

@ -21,6 +21,9 @@ import shutil
import sys
import time
import traceback
import gc
import re
import types
import subprocess as _sp
from pathlib import Path
from typing import Any, Callable
@ -1259,7 +1262,6 @@ def _run_mlx_training(event_queue, stop_queue, config):
Mirrors the event_queue protocol so the parent process pump works unchanged.
"""
import time
import gc
import math
import threading
import queue as _queue
@ -2020,121 +2022,13 @@ def run_training_process(
)
# ── 1d. Stub torchao on Windows ROCm ──
# torchao (pulled in by transformers.quantizers) imports
# torch.distributed._functional_collectives at module level, which imports
# distributed_c10d.py unconditionally — that file crashes on Windows ROCm
# because torch._C._distributed_c10d (the RCCL backend) is absent.
# torch/distributed/__init__.py itself is guarded by `if is_available()`
# so `import torch.distributed` alone is safe; the crash only comes via
# torchao's import chain. Stubbing torchao short-circuits it entirely.
# _StubSubpackageFinder handles any depth of torchao.xxx.yyy imports.
import types as _types
import importlib.machinery as _ilm
import importlib.abc as _ilabc
# Shared with the export worker; see core/_torchao_stub.py for the full
# rationale (torchao -> torch.distributed._functional_collectives crashes on
# Windows ROCm because the RCCL backend is absent). No-op off Windows ROCm.
# Must run before any import of transformers / unsloth_zoo.
from core._torchao_stub import install_torchao_windows_rocm_stub
_STUB_SENTINEL = object()
# Metaclass for stub types so that isinstance(x, StubClass) returns False
# instead of raising TypeError ("arg 2 must be a type").
# peft/tuners/lora/torchao.py does:
# from torchao.dtypes import AffineQuantizedTensor, LinearActivationQuantizedTensor
# isinstance(weight, (AffineQuantizedTensor, LinearActivationQuantizedTensor))
# If those names resolve to stub modules rather than types, isinstance() raises.
class _StubTypeMeta(type):
def __instancecheck__(cls, instance):
return False
def __subclasscheck__(cls, subclass):
return False
def __getattr__(cls, attr):
if attr.startswith("__"):
raise AttributeError(attr)
child = _StubTypeMeta(attr, (), {})
setattr(cls, attr, child)
return child
def __call__(cls, *args, **kwargs):
return None
def _make_stub_type(name):
"""Stub class: accepted by isinstance() (always False), supports attr access."""
return _StubTypeMeta(name, (), {})
def _make_mod_stub(mod_name):
m = _types.ModuleType(mod_name)
m.__path__ = []
m.__package__ = mod_name
m._unsloth_stub = _STUB_SENTINEL
m.__spec__ = _ilm.ModuleSpec(mod_name, loader = None, is_package = True)
def _ga(attr, _m = m, _n = mod_name):
if attr.startswith("__"):
raise AttributeError(attr)
# Return a stub CLASS (not a module) so that isinstance(x, attr)
# works and returns False instead of raising TypeError.
child = _make_stub_type(f"{_n}.{attr}")
setattr(_m, attr, child)
return child
m.__getattr__ = _ga
return m
class _StubSubpackageLoader(_ilabc.Loader):
def __init__(self, mod_name):
self._mod_name = mod_name
def create_module(self, spec):
return _make_mod_stub(self._mod_name)
def exec_module(self, module):
pass
class _StubSubpackageFinder(_ilabc.MetaPathFinder):
def find_spec(self, fullname, path, target = None):
if "." not in fullname:
return None
parent = sys.modules.get(fullname.rsplit(".", 1)[0])
if parent is None:
return None
if getattr(parent, "_unsloth_stub", None) is not _STUB_SENTINEL:
return None
return _ilm.ModuleSpec(
fullname, _StubSubpackageLoader(fullname), is_package = True
)
# Only stub torchao on Windows ROCm hosts -- on Windows CUDA (NVIDIA) torchao
# is real and shadowing it breaks torchao-based quantization paths.
# Gate on the active torch runtime, not env-var presence -- HIP_PATH /
# ROCM_PATH stay set after a user installs the HIP SDK and reverts to a
# CUDA torch wheel. AMD SDK / Radeon ROCm wheels may not set torch.version.hip
# but still encode "rocm" in torch.__version__, so accept either.
_is_win32_rocm = False
if sys.platform == "win32":
try:
import torch as _torch_probe
_is_win32_rocm = bool(
getattr(getattr(_torch_probe, "version", None), "hip", None)
or "rocm" in getattr(_torch_probe, "__version__", "").lower()
)
del _torch_probe
except Exception:
pass
if _is_win32_rocm:
# Register the finder only on Windows ROCm -- on other platforms there
# are no stub modules seeded, so appending is a pure accumulation.
sys.meta_path.append(_StubSubpackageFinder())
# Seed torchao top-level + key submodules; the finder handles the rest.
for _tao_name in (
"torchao",
"torchao.quantization",
"torchao.dtypes",
"torchao.float8",
"torchao.utils",
):
if _tao_name not in sys.modules:
sys.modules[_tao_name] = _make_mod_stub(_tao_name)
install_torchao_windows_rocm_stub()
# ── 1e. Ensure torch.distributed helper attrs are present ──
# Single-GPU training never initialises the process group, so these helpers
@ -2155,7 +2049,7 @@ def run_training_process(
if not hasattr(_td, _name):
setattr(_td, _name, _stub)
except Exception:
_td_mock = _types.ModuleType("torch.distributed")
_td_mock = types.ModuleType("torch.distributed")
for _name, _stub in _td_stubs.items():
setattr(_td_mock, _name, _stub)
sys.modules["torch.distributed"] = _td_mock
@ -2269,17 +2163,13 @@ def run_training_process(
# (e.g. "2.11.0+rocm7.13.0" or "2.9.0+rocmsdk20251116"); fall back
# to that string when version.hip is missing.
def _hip_ver_at_least(major: int, minor: int) -> bool:
import re as _re_ver
_hip_str = getattr(
getattr(_torch_for_rocm, "version", None), "hip", None
)
if not _hip_str:
# Try the standard "+rocmX.Y.Z" embedded version first
# (e.g. "2.11.0+rocm7.13.0").
_ver_match = _re_ver.search(
r"rocm(\d+)\.(\d+)", _build_version_for_rocm
)
_ver_match = re.search(r"rocm(\d+)\.(\d+)", _build_version_for_rocm)
if _ver_match:
return (
int(_ver_match.group(1)),

View file

@ -866,8 +866,6 @@ def _strip_crossorigin(html_bytes: bytes) -> bytes:
@font-face downloads to fail silently. Stripping the attribute
makes them regular same-origin fetches that work on any protocol.
"""
import re as _re
html = html_bytes.decode("utf-8")
html = _re.sub(r'\s+crossorigin(?:="[^"]*")?', "", html)
return html.encode("utf-8")

View file

@ -16,8 +16,16 @@ Usage:
...
"""
import copy
import gc
import glob
import os
import platform
import re
import subprocess
import sys
import types
from importlib.metadata import PackageNotFoundError, version as pkg_version
import structlog
from loggers import get_logger
from enum import Enum
@ -178,8 +186,6 @@ def clear_gpu_cache():
Clear GPU memory cache for the current device.
Safe to call on any platform no-ops gracefully.
"""
import gc
gc.collect()
device = get_device()
@ -361,8 +367,6 @@ def get_package_versions() -> Dict[str, Optional[str]]:
Returns dict with keys: unsloth, torch, transformers, cuda.
Missing packages yield None.
"""
from importlib.metadata import version as pkg_version, PackageNotFoundError
packages = ("unsloth", "torch", "transformers")
versions: Dict[str, Optional[str]] = {}
@ -481,9 +485,6 @@ def _read_apple_gpu_stats() -> Dict[str, Any]:
Returns dict with utilization_pct, vram_used_bytes (system-wide GPU memory).
Returns empty dict on failure.
"""
import subprocess
import re
try:
result = subprocess.run(
["ioreg", "-r", "-c", "AGXAccelerator"],
@ -510,12 +511,10 @@ def _read_apple_gpu_stats() -> Dict[str, Any]:
def _rocm_linux_sysfs_gpu_busy_pct() -> Optional[float]:
"""Query AMD GPU compute utilization via Linux DRM sysfs gpu_busy_percent."""
import glob as _glob
if platform.system() != "Linux":
return None
try:
files = _glob.glob("/sys/class/drm/card*/device/gpu_busy_percent")
files = glob.glob("/sys/class/drm/card*/device/gpu_busy_percent")
if not files:
return None
values = [int(open(f).read().strip()) for f in files]
@ -526,12 +525,10 @@ def _rocm_linux_sysfs_gpu_busy_pct() -> Optional[float]:
def _rocm_linux_sysfs_temp_c() -> Optional[float]:
"""Query AMD GPU edge temperature via Linux DRM hwmon sysfs (temp1_input, millidegrees C)."""
import glob as _glob
if platform.system() != "Linux":
return None
try:
files = _glob.glob("/sys/class/drm/card*/device/hwmon/hwmon*/temp1_input")
files = glob.glob("/sys/class/drm/card*/device/hwmon/hwmon*/temp1_input")
if not files:
return None
temps = [int(open(f).read().strip()) / 1000.0 for f in files]
@ -542,8 +539,6 @@ def _rocm_linux_sysfs_temp_c() -> Optional[float]:
def _rocm_linux_sysfs_power_w() -> Optional[float]:
"""Query AMD GPU average power draw via Linux DRM hwmon sysfs (microwatts)."""
import glob as _glob
if platform.system() != "Linux":
return None
try:
@ -551,7 +546,7 @@ def _rocm_linux_sysfs_power_w() -> Optional[float]:
"/sys/class/drm/card*/device/hwmon/hwmon*/power1_average",
"/sys/class/drm/card*/device/hwmon/hwmon*/power1_input",
):
files = _glob.glob(pattern)
files = glob.glob(pattern)
if files:
watts = sum(int(open(f).read().strip()) / 1_000_000.0 for f in files)
return round(watts, 1)
@ -562,8 +557,6 @@ def _rocm_linux_sysfs_power_w() -> Optional[float]:
def _rocm_windows_perf_counter_gpu_util_pct() -> Optional[float]:
"""Query AMD GPU compute utilization via Windows Performance Counters (3D engine nodes)."""
import subprocess as _sp
if platform.system() != "Windows":
return None
try:
@ -572,7 +565,7 @@ def _rocm_windows_perf_counter_gpu_util_pct() -> Optional[float]:
" -ErrorAction SilentlyContinue).CounterSamples;"
"if($s){[math]::Min(($s|Measure-Object CookedValue -Sum).Sum,100)}else{-1}"
)
r = _sp.run(
r = subprocess.run(
["powershell", "-NoProfile", "-NonInteractive", "-Command", ps],
capture_output = True,
text = True,
@ -593,13 +586,11 @@ def _rocm_linux_sysfs_vram_gb() -> tuple[Optional[float], Optional[float]]:
updates in real-time across all processes. No tools required.
Returns (used_gb, total_gb) or (None, None) on failure.
"""
import glob as _glob
if platform.system() != "Linux":
return None, None
try:
used_files = _glob.glob("/sys/class/drm/card*/device/mem_info_vram_used")
total_files = _glob.glob("/sys/class/drm/card*/device/mem_info_vram_total")
used_files = glob.glob("/sys/class/drm/card*/device/mem_info_vram_used")
total_files = glob.glob("/sys/class/drm/card*/device/mem_info_vram_total")
if not used_files or not total_files:
return None, None
used_bytes = sum(int(open(f).read().strip()) for f in used_files)
@ -618,8 +609,6 @@ def _rocm_windows_perf_counter_vram_gb() -> tuple[Optional[float], Optional[floa
usage accurately. Works for any GPU vendor without amd-smi or nvidia-smi.
Returns (used_gb, total_gb) or (None, None) on failure.
"""
import subprocess as _sp
if platform.system() != "Windows":
return None, None
try:
@ -628,7 +617,7 @@ def _rocm_windows_perf_counter_vram_gb() -> tuple[Optional[float], Optional[floa
" -ErrorAction SilentlyContinue).CounterSamples;"
"if($s){($s|Measure-Object CookedValue -Sum).Sum}else{-1}"
)
r = _sp.run(
r = subprocess.run(
["powershell", "-NoProfile", "-NonInteractive", "-Command", ps],
capture_output = True,
text = True,
@ -1160,17 +1149,12 @@ def _load_config_for_gpu_estimate(model_name: str, hf_token: Optional[str] = Non
def _determine_attention_impl_for_gpu_estimate(config) -> str:
import copy as _copy
# torch.distributed is incomplete on Windows ROCm — torch._C is a C
# extension (not a package), so Python cannot import the submodule
# torch._C._distributed_c10d that torch.distributed depends on.
# Inject an empty stub into sys.modules BEFORE importing torch.distributed
# so the import succeeds, then patch the missing process-group helpers.
import sys as _sys
import types as _types
if _sys.platform == "win32" and IS_ROCM:
if sys.platform == "win32" and IS_ROCM:
# Dummy class for any name torch.distributed tries to import from these stubs
class _Dummy:
pass
@ -1180,8 +1164,8 @@ def _determine_attention_impl_for_gpu_estimate(config) -> str:
"torch._C._distributed_autograd",
"torch._C._distributed_rpc",
):
if _c10d_name not in _sys.modules:
_stub = _types.ModuleType(_c10d_name)
if _c10d_name not in sys.modules:
_stub = types.ModuleType(_c10d_name)
# torch.distributed imports these names from _distributed_c10d;
# provide no-op dummies so the import doesn't raise AttributeError.
for _sym in (
@ -1200,7 +1184,7 @@ def _determine_attention_impl_for_gpu_estimate(config) -> str:
"BuiltinCommHookType",
):
setattr(_stub, _sym, _Dummy)
_sys.modules[_c10d_name] = _stub
sys.modules[_c10d_name] = _stub
try:
import torch.distributed as _td
@ -1225,7 +1209,7 @@ def _determine_attention_impl_for_gpu_estimate(config) -> str:
# `sub_configs` and propagates to nested text_config / sub-configs, so a
# shallow copy still mutates those shared inner objects on the cached
# config returned by _load_config_for_gpu_estimate. Deepcopy isolates them.
config_copy = _copy.deepcopy(config)
config_copy = copy.deepcopy(config)
model_class = None
for auto_model in (AutoModelForCausalLM, AutoModel):
@ -2026,8 +2010,6 @@ def safe_num_proc(desired: Optional[int] = None) -> int:
Returns:
A safe integer 1.
"""
import sys
# Windows and macOS use 'spawn' for multiprocessing -- the overhead of
# re-importing torch/transformers/unsloth per worker is typically slower
# than single-process.
@ -2078,8 +2060,6 @@ def dataset_map_num_proc(desired: Optional[int] = None) -> Optional[int]:
``datasets`` treats ``num_proc=1`` as multiprocessing (creates ``Pool(1)``).
Only ``num_proc=None`` guarantees in-process execution.
"""
import sys
if sys.platform in ("win32", "darwin"):
return None
return safe_num_proc(desired)

View file

@ -12,8 +12,10 @@ PATH to point at the venv.
from __future__ import annotations
import glob
import os
import platform
import re
import shutil
import subprocess
import sys
@ -209,8 +211,6 @@ def _detect_rocm_version() -> tuple[int, int] | None:
# for the rocm-core package version. Matches the chain in
# install.sh::get_torch_index_url so `unsloth studio update` behaves
# the same as a fresh `curl | sh` install.
import re as _re_pkg
for cmd in (
["dpkg-query", "-W", "-f=${Version}\n", "rocm-core"],
["rpm", "-q", "--qf", "%{VERSION}\n", "rocm-core"],
@ -232,8 +232,8 @@ def _detect_rocm_version() -> tuple[int, int] | None:
continue
raw = result.stdout.strip()
# dpkg can prepend an epoch ("1:6.3.0-1"); strip it before parsing.
raw = _re_pkg.sub(r"^\d+:", "", raw)
m = _re_pkg.match(r"(\d+)[.-](\d+)", raw)
raw = re.sub(r"^\d+:", "", raw)
m = re.match(r"(\d+)[.-](\d+)", raw)
if m:
return int(m.group(1)), int(m.group(2))
@ -274,8 +274,6 @@ def _detect_windows_gfx_arch() -> str | None:
enumeration order) and HIP_VISIBLE_DEVICES / ROCR_VISIBLE_DEVICES selects
which one to install for. The first GPU is used when no env var is set.
"""
import re
# 1. Explicit override (matches PowerShell installer's env-var path).
_override = os.environ.get("UNSLOTH_ROCM_GFX_ARCH")
if _override and _override.strip():
@ -366,9 +364,7 @@ def _detect_bnb_rocm_dll_ver() -> str | None:
bitsandbytes uses importlib.util.find_spec so it is safe to call before
BNB is imported.
"""
import glob
import importlib.util
import re
spec = importlib.util.find_spec("bitsandbytes")
if spec is None or not spec.submodule_search_locations:
@ -387,8 +383,6 @@ def _detect_bnb_rocm_dll_ver() -> str | None:
def _has_rocm_gpu() -> bool:
"""Return True only if an actual AMD GPU is visible (not just ROCm tools installed)."""
import re
for cmd, check_fn in (
# rocminfo: look for a real gfx GPU id (3-4 chars, nonzero first digit).
# gfx000 is the CPU agent; ROCm 6.1+ also emits generic ISA lines like
@ -469,7 +463,6 @@ def _detect_amd_gfx_codes() -> list[str]:
amd-smi but no rocminfo. Returns an empty list when no probe yields
a gfx target.
"""
import re
def _extract(text: str) -> list[str]:
codes = re.findall(r"gfx([1-9][0-9a-z]{2,3})", text.lower())
@ -510,28 +503,24 @@ def _install_bnb_windows_rocm() -> bool:
The continuous-release wheel is intentionally mismatched: the filename
encodes version 1.33.7.preview (parsed as 1.33.7rc0 by PEP 440) while the
wheel metadata reports 0.50.0.dev0. uv rejects this filename/metadata
mismatch; set UV_SKIP_WHEEL_FILENAME_CHECK=1 to bypass that check, then
restore the previous value (or remove the var) when done.
mismatch -- and bypassing it with UV_SKIP_WHEEL_FILENAME_CHECK still leaves
uv mangling the bitsandbytes install. Per the AMD install guide
(https://unsloth.ai/docs/get-started/install/amd/amd-hackathon) the wheel
must be installed with plain pip, not uv, so we force pip here
(force_pip=True). plain pip performs no wheel filename/metadata check.
"""
_bnb_win_url = _BNB_ROCM_PRERELEASE_URLS.get("win_amd64")
if _bnb_win_url is None:
return False
_old = os.environ.get("UV_SKIP_WHEEL_FILENAME_CHECK")
os.environ["UV_SKIP_WHEEL_FILENAME_CHECK"] = "1"
try:
_ok = pip_install_try(
"bitsandbytes (AMD Windows, pre-release main)",
"--force-reinstall",
"--no-cache-dir",
"--no-deps",
_bnb_win_url,
constrain = False,
)
finally:
if _old is None:
os.environ.pop("UV_SKIP_WHEEL_FILENAME_CHECK", None)
else:
os.environ["UV_SKIP_WHEEL_FILENAME_CHECK"] = _old
_ok = pip_install_try(
"bitsandbytes (AMD Windows, pre-release main)",
"--force-reinstall",
"--no-cache-dir",
"--no-deps",
_bnb_win_url,
constrain = False,
force_pip = True,
)
if not _ok:
return False
# After install: detect the actual ROCm DLL suffix shipped in the wheel and

View file

@ -818,11 +818,11 @@ if (-not $HasNvidiaSmi) {
# Ordered most-specific first; first match wins.
elseif ($ROCmGpuLabel) {
$nameArchTable = @(
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4
@{ P = "8060S|890M|Strix Halo|HX 37[05]|HX 38[05]|AI 9 HX"; A = "gfx1151" } # RDNA 3.5 iGPU (Strix Halo / Radeon 8060S retail)
@{ P = "880M|Strix Point|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]"; A = "gfx1150" } # RDNA 3.5 iGPU (Strix Point)
@{ P = "RX 7900|RX 7800|RX 7700(?! S)"; A = "gfx1100" } # RDNA 3 desktop
@{ P = "8060S|890M|Strix Halo|HX 37[05]|HX 38[05]|AI 9 HX"; A = "gfx1151" } # RDNA 3.5 iGPU (Strix Halo / Radeon 8060S retail)
@{ P = "880M|Strix Point|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]"; A = "gfx1150" } # RDNA 3.5 iGPU (Strix Point)
@{ P = "RX 7900|RX 7800|RX 7700(?! S)"; A = "gfx1100" } # RDNA 3 desktop
@{ P = "RX 7600"; A = "gfx1102" } # RDNA 3
@{ P = "780M|760M|740M|Phoenix"; A = "gfx1103" } # RDNA 3 iGPU (Phoenix)
)
@ -2037,10 +2037,10 @@ if ($HasROCm -and $CuTag -eq "cpu") {
$amdIndexBase = if ($env:UNSLOTH_ROCM_WINDOWS_MIRROR) { $env:UNSLOTH_ROCM_WINDOWS_MIRROR.TrimEnd('/') } else { "https://repo.amd.com/rocm/whl" }
$archFamilyMap = @{
"gfx1201" = "gfx120X-all"; "gfx1200" = "gfx120X-all" # RDNA 4
"gfx1151" = "gfx1151"; "gfx1150" = "gfx1150" # RDNA 3.5 (Strix Halo/Point)
"gfx1103" = "gfx110X-all"; "gfx1102" = "gfx110X-all" # RDNA 3
"gfx1151" = "gfx1151"; "gfx1150" = "gfx1150" # RDNA 3.5 (Strix Halo/Point)
"gfx1103" = "gfx110X-all"; "gfx1102" = "gfx110X-all" # RDNA 3
"gfx1101" = "gfx110X-all"; "gfx1100" = "gfx110X-all"
"gfx90a" = "gfx90a"; "gfx908" = "gfx908" # MI200/MI100
"gfx90a" = "gfx90a"; "gfx908" = "gfx908" # MI200/MI100
}
# gfx120X and Strix have a null _grouped_mm kernel on torch <2.11.0.
# Mirrors the $torchFloorMap in install.ps1 so both installers enforce

View file

@ -707,13 +707,13 @@ elif [ "$_setup_amd_detected" = true ]; then
# Name-based arch inference when tools don't report gfx (mirrors setup.ps1 nameArchTable)
elif [ -z "$_setup_gfx" ] && [ -n "$_setup_mkt" ]; then
case "$_setup_mkt" in
*"9070 XT"*|*9080*) _setup_gfx="gfx1201" ;; # RDNA 4
*9070*|*9060*) _setup_gfx="gfx1200" ;; # RDNA 4
*"9070 XT"*|*9080*) _setup_gfx="gfx1201" ;; # RDNA 4
*9070*|*9060*) _setup_gfx="gfx1200" ;; # RDNA 4
*"8060S"*|*"890M"*|*"Strix Halo"*|*"HX 37"*|*"HX 38"*|*"AI 9 HX"*) _setup_gfx="gfx1151" ;; # RDNA 3.5 iGPU
*"880M"*|*"Strix Point"*|*"AI 9 36"*|*"AI 7 35"*|*"AI 5 34"*) _setup_gfx="gfx1150" ;; # RDNA 3.5 iGPU
*"RX 7900"*|*"RX 7800"*|*"RX 7700"*) _setup_gfx="gfx1100" ;; # RDNA 3 desktop
*"RX 7600"*) _setup_gfx="gfx1102" ;; # RDNA 3
*"780M"*|*"760M"*|*"740M"*|*"Phoenix"*) _setup_gfx="gfx1103" ;; # RDNA 3 iGPU
*"880M"*|*"Strix Point"*|*"AI 9 36"*|*"AI 7 35"*|*"AI 5 34"*) _setup_gfx="gfx1150" ;; # RDNA 3.5 iGPU
*"RX 7900"*|*"RX 7800"*|*"RX 7700"*) _setup_gfx="gfx1100" ;; # RDNA 3 desktop
*"RX 7600"*) _setup_gfx="gfx1102" ;; # RDNA 3
*"780M"*|*"760M"*|*"740M"*|*"Phoenix"*) _setup_gfx="gfx1103" ;; # RDNA 3 iGPU
esac
if [ -n "$_setup_gfx" ]; then
substep "gfx arch inferred from GPU name: $_setup_gfx"