refactor uv support

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-05-28 11:54:08 +02:00
parent 1bd00e4e80
commit 89e728c9d5
8 changed files with 153 additions and 33 deletions
+2
View File
@@ -51,6 +51,8 @@ Plus continued work on modernization of codebase: UI is now fully TypeScript bas
see *settings -> huggingface -> download method* for options
- **Nunchaku** consider *DEV* builds when auto-installing
- **Gallery** add clear cache button to folder menu
- **UV** much updated `--uv` support for fast installs
now also supports global `uv` if present in the system
- **Changes**
- all **Guidance** params are now set to *-1* by default to allow using model defaults and avoid confusion with different model behaviour
log will print default values used by model if not set by user
+63 -14
View File
@@ -208,6 +208,37 @@ def uninstall(package, quiet = False):
return txt
def uv_info():
uv_version = None
uv_cache_dir = None
uv_cache_active = False
uv_local = os.path.join(sys.prefix, "bin", "uv") # Prefer uv inside the venv
if os.path.exists(uv_local):
uv_version = subprocess.check_output([uv_local, "--version"], text=True).strip()
uv_cache_dir = subprocess.check_output([uv_local, "cache", "dir"], text=True).strip()
uv_global = shutil.which("uv") # Fallback: system uv
if uv_global:
uv_version = subprocess.check_output([uv_global, "--version"], text=True).strip()
uv_cache_dir = subprocess.check_output([uv_global, "cache", "dir"], text=True).strip()
uv_cache_disabled = os.environ.get("UV_NO_CACHE") == "1"
site = next(p for p in sys.path if p.endswith("site-packages"))
if uv_cache_dir and not uv_cache_disabled:
for root, _dirs, files in os.walk(site):
for f in files:
full = os.path.join(root, f)
try:
st = os.stat(full)
except FileNotFoundError:
continue
if st.st_nlink > 1: # Hardlink count > 1 means deduped
uv_cache_active = True
cache_path = os.path.join(uv_cache_dir, f) # Or check if inode matches something in cache
if os.path.exists(cache_path):
if os.stat(cache_path).st_ino == st.st_ino:
uv_cache_active = True
log.debug(f'Package manager: app=uv version="{uv_version}" folder="{uv_cache_dir}" dedup={uv_cache_active}')
def run(cmd: str, *nargs: str, **kwargs):
options = {
"check": False,
@@ -253,7 +284,8 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, *, uv = True, constr
log.warning('Offline mode enabled')
return None, 'offline'
package = arg.replace("install", "").replace("--upgrade", "").replace("--no-deps", "").replace("--force-reinstall", "").strip()
uv = uv and args.uv and not package.startswith('git+')
# uv = uv and args.uv and not package.startswith('git+')
uv = uv and args.uv
pipCmd = "uv pip" if uv else "pip"
if not quiet and '-r ' not in arg:
log.info(f'Install: package="{package}" mode={"uv" if uv else "pip"}')
@@ -264,20 +296,26 @@ def pip(arg: str, ignore: bool = False, quiet: bool = True, *, uv = True, constr
all_args.append(arg)
if env_args:
all_args.append(env_args)
if constraints and "-c " not in env_args:
if constraints and "-c " not in env_args and arg.startswith("install"):
all_args.append("-c constraints.txt")
if not quiet:
log.debug(f'Running: {pipCmd}="{" ".join(all_args)}"')
result, output = run(sys.executable, "-m", pipCmd, *all_args)
if uv:
result, output = run("uv", "pip", *all_args)
else:
result, output = run(sys.executable, "-m", pipCmd, *all_args)
if len(result.stderr) > 0:
if uv and result.returncode != 0:
print('HERE1', result)
print('HERE2', output)
log.warning(f'Install: cmd="{pipCmd}" args="{" ".join(all_args)}" cannot use uv, fallback to pip')
debug(f'Install: uv pip error: {result.stderr}')
cleanup_broken_packages()
return pip(originalArg, ignore, quiet, uv=False)
debug(f'Install {pipCmd}: {output}')
if os.environ.get('SD_INSTALL_DEBUG', None) is not None:
log.debug(f'PIP cmd="{pipCmd}": {output}')
if result.returncode != 0 and not ignore:
errors.append(f'pip: {package}')
log.error(f'Install: {pipCmd}: {arg}')
@@ -294,7 +332,7 @@ def install(package, friendly: str | None = None, ignore: bool = False, reinstal
if args.reinstall or args.upgrade:
global quick_allowed # pylint: disable=global-statement
quick_allowed = False
if (args.reinstall) or (reinstall) or (not installed(package, friendly, quiet=quiet)):
if args.reinstall or reinstall or not installed(package, friendly, quiet=quiet):
deps = '' if not no_deps else '--no-deps '
isolation = '' if not no_build_isolation else '--no-build-isolation '
cmd = f"install{' --upgrade' if not args.uv else ''}{' --force-reinstall' if force else ''} {deps}{isolation}{package}"
@@ -432,6 +470,8 @@ def get_platform():
'locale': locale.getlocale(),
'setuptools': package_version('setuptools'),
'docker': os.environ.get('SD_DOCKER', None) is not None,
'pip': package_version('pip'),
'uv': package_version('uv'),
# 'host': platform.node(),
# 'version': platform.version(),
}
@@ -539,9 +579,9 @@ def check_transformers():
log.info(f'Install: package="transformers" version={target_transformers}')
else:
log.info(f'Update: package="transformers" current={pkg_transformers.version} target={target_transformers}')
pip('uninstall --yes transformers', ignore=True, quiet=True, uv=False)
pip(f'install --upgrade tokenizers=={target_tokenizers}', ignore=False, quiet=True, uv=False)
pip(f'install --upgrade transformers=={target_transformers}', ignore=False, quiet=True, uv=False)
pip('uninstall --yes transformers', ignore=True, quiet=True)
pip(f'install --upgrade tokenizers=={target_tokenizers}', ignore=False, quiet=True)
pip(f'install --upgrade transformers=={target_transformers}', ignore=False, quiet=True)
else:
# Git commit-pinned version
current = opts.get('transformers_version', '')
@@ -550,9 +590,9 @@ def check_transformers():
log.info(f'Install: package="transformers" commit={target_commit}')
else:
log.info(f'Update: package="transformers" current={pkg_transformers.version} hash={current} target={target_commit}')
pip('uninstall --yes transformers', ignore=True, quiet=True, uv=False)
pip(f'install --upgrade tokenizers=={target_tokenizers}', ignore=False, quiet=True, uv=False)
pip(f'install --upgrade git+https://github.com/huggingface/transformers@{target_commit}', ignore=False, quiet=True, uv=False)
pip('uninstall --yes transformers', ignore=True, quiet=True)
pip(f'install --upgrade tokenizers=={target_tokenizers}', ignore=False, quiet=True)
pip(f'install --upgrade git+https://github.com/huggingface/transformers@{target_commit}', ignore=False, quiet=True)
global transformers_commit # pylint: disable=global-statement
transformers_commit = target_commit
ts('transformers', t_start)
@@ -1490,6 +1530,8 @@ def check_venv():
import site
pkg_path = [try_relpath(p) for p in site.getsitepackages() if os.path.exists(p)]
log.debug(f'Packages: prefix={try_relpath(sys.prefix)} site={pkg_path}')
if args.uv:
uv_info()
for p in pkg_path:
invalid = []
for f in os.listdir(p):
@@ -1791,12 +1833,18 @@ def read_options():
def ensure_base_requirements():
t_start = time.time()
setuptools_version = '69.5.1'
if (args.uv or '--uv' in sys.argv) and (shutil.which('uv') is not None): # early enable uv
args.uv = True # pylint: disable=attribute-defined-outside-init
uv_info()
def update_setuptools():
local_log = logging.getLogger('sdnext.installer')
global setuptools, distutils # pylint: disable=global-statement
# python may ship with incompatible setuptools
subprocess.run(f'"{sys.executable}" -m pip install setuptools=={setuptools_version}', shell=True, check=False, env=os.environ, capture_output=True)
if args.uv:
subprocess.run(f'uv pip install setuptools=={setuptools_version} wheel', shell=True, check=False, env=os.environ, capture_output=True)
else:
subprocess.run(f'"{sys.executable}" -m pip install setuptools=={setuptools_version} wheel', shell=True, check=False, env=os.environ, capture_output=True)
# need to delete all references to modules to be able to reload them otherwise python will use cached version
modules = [m for m in sys.modules if m.startswith('setuptools') or m.startswith('distutils')]
for m in modules:
@@ -1825,6 +1873,7 @@ def ensure_base_requirements():
try:
global setuptools # pylint: disable=global-statement
import wheel # pylint: disable=unused-import
import setuptools # pylint: disable=redefined-outer-name
if setuptools.__version__ != setuptools_version:
update_setuptools()
@@ -1832,13 +1881,13 @@ def ensure_base_requirements():
update_setuptools()
# used by installler itself so must be installed before requirements
install('rich==14.1.0', 'rich', quiet=True)
install('rich==15.0.0', 'rich', quiet=True)
install('psutil', 'psutil', quiet=True)
install('requests==2.32.3', 'requests', quiet=True)
ts('base', t_start)
# startup
# startup
ensure_base_requirements()
from modules.logger import setup_logging # must be loaded after ensure_base_requirements
from modules.logger import log as log_instance
+2 -1
View File
@@ -5,6 +5,7 @@ import os
import sys
import time
import shlex
import shutil
import subprocess
import installer
@@ -255,7 +256,7 @@ def main():
log.info(f'Args: {sys.argv[1:]}')
if not args.skip_env and not args.skip_all:
installer.set_environment()
if args.uv:
if args.uv and shutil.which('uv') is None:
installer.install('uv', 'uv')
installer.install_gradio()
installer.check_torch()
+3 -3
View File
@@ -53,7 +53,7 @@ def install_nunchaku(force=False):
if url is not None:
cmd = f'install --upgrade {url}'
log.debug(f'Nunchaku: install="{url}"')
result, _output = pip(cmd, uv=False, ignore=not force, quiet=not force)
result, _output = pip(cmd, ignore=not force, quiet=not force)
return result.returncode == 0
else:
import torch
@@ -75,7 +75,7 @@ def install_nunchaku(force=False):
fn = f'nunchaku-{v}+{cuda_ver}torch{torch_ver}-cp{python_ver}-cp{python_ver}-{suffix}.whl'
else:
fn = f'nunchaku-{v}+{cuda_ver}torch{torch_ver}-cp{python_ver}-cp{python_ver}-{suffix}.whl'
result, _output = pip(f'install --upgrade {url+fn}', uv=False, ignore=True, quiet=True)
result, _output = pip(f'install --upgrade {url+fn}', ignore=True, quiet=True)
if (result is None) or (_output == 'offline'):
log.error(f'Nunchaku: install url="{url+fn}" offline mode')
return False
@@ -85,7 +85,7 @@ def install_nunchaku(force=False):
log.info(f'Nunchaku: install url="{url}"')
return True
fn = f'nunchaku-{v}+torch{torch_ver}-cp{python_ver}-cp{python_ver}-{suffix}.whl'
result, _output = pip(f'install --upgrade {url+fn}', uv=False, ignore=True, quiet=True)
result, _output = pip(f'install --upgrade {url+fn}', ignore=True, quiet=True)
if (result is None) or (_output == 'offline'):
log.error(f'Nunchaku: install url="{url+fn}" offline mode')
return False
+4 -4
View File
@@ -22,7 +22,7 @@ ftfy
# versioned
fastapi==0.124.4
rich==14.1.0
rich==15.0.0
safetensors==0.8.0rc0
peft==0.19.1
httpx==0.28.1
@@ -30,12 +30,12 @@ requests==2.32.3
tqdm==4.67.3
accelerate==1.13.0
einops==0.8.1
huggingface_hub==1.15.0
huggingface_hub==1.16.4
hf_xet==1.5.0
numpy==2.1.2
pandas==2.3.1
protobuf==6.33.5
pytorch_lightning==2.6.1
protobuf==7.35.0
pytorch_lightning==2.6.5
urllib3==1.26.19
Pillow==12.2.0
timm==1.0.24
+24 -4
View File
@@ -16,24 +16,44 @@ goto :show_stdout_stderr
:check_pip
%PYTHON% -mpip --help >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :start_venv
if %ERRORLEVEL% == 0 goto :check_uv_flag
if "%PIP_INSTALLER_LOCATION%" == "" goto :show_stdout_stderr
%PYTHON% "%PIP_INSTALLER_LOCATION%" >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :start_venv
if %ERRORLEVEL% == 0 goto :check_uv_flag
echo Cannot install pip
goto :show_stdout_stderr
:check_uv_flag
set use_uv=0
for %%i in (%*) do (
if /I "%%i"=="--uv" set use_uv=1
)
goto :start_venv
:start_venv
if ["%VENV_DIR%"] == ["-"] goto :skip_venv
if ["%SKIP_VENV%"] == ["1"] goto :skip_venv
if "%use_uv%" == "1" (
where uv >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo Warning: uv is not installed, falling back to python venv
set use_uv=0
)
)
dir "%VENV_DIR%\Scripts\Python.exe" >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :activate_venv
for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i"
echo Using python: %PYTHON_FULLNAME%
echo Creating VENV: %VENV_DIR%
%PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt
if "%use_uv%" == "1" (
echo Creating VENV: UV
uv venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt
) else (
echo Creating VENV: VENV
%PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt
)
if %ERRORLEVEL% == 0 goto :activate_venv
echo Failed creating VENV: "%VENV_DIR%"
goto :show_stdout_stderr
+22 -2
View File
@@ -22,6 +22,14 @@ function ShowStdOutStdErr {
$PYTHON = if ($env:PYTHON) { $env:PYTHON } else { 'python' }
$VENV_DIR = if ($env:VENV_DIR) { $env:VENV_DIR } else { Join-Path $PSScriptRoot 'venv' }
$use_uv = $false
foreach ($arg in $args) {
if ($arg -eq '--uv') {
$use_uv = $true
break
}
}
New-Item -Path 'tmp' -ItemType Directory -ErrorAction SilentlyContinue
try {
@@ -58,9 +66,21 @@ if ($VENV_DIR -ne '-' -and $env:SKIP_VENV -ne '1') {
$PYTHON_FULLNAME = & $PYTHON -c 'import sys; print(sys.executable)'
Write-Output "Using python: $PYTHON_FULLNAME"
Write-Output "Creating VENV: $VENV_DIR"
& $PYTHON_FULLNAME -m venv $VENV_DIR 2>tmp\stderr.txt | Out-File tmp\stdout.txt
if ($use_uv) {
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
Write-Output 'Warning: uv is not installed, falling back to python venv'
$use_uv = $false
}
}
if ($use_uv) {
Write-Output "Creating VENV: UV"
& uv venv $VENV_DIR 2>tmp\stderr.txt | Out-File tmp\stdout.txt
} else {
Write-Output "Creating VENV: VENV"
& $PYTHON_FULLNAME -m venv $VENV_DIR 2>tmp\stderr.txt | Out-File tmp\stdout.txt
}
if ($LASTEXITCODE -eq 0) {
$PYTHON = Join-Path $VENV_DIR 'Scripts\Python.exe'
+33 -5
View File
@@ -7,6 +7,7 @@
cd -- "$(dirname -- "$0")"
can_run_as_root=0
use_uv=0
export ERROR_REPORTING=FALSE
export PIP_IGNORE_INSTALLED=0
@@ -34,6 +35,15 @@ then
venv_dir="venv"
fi
for arg in "$@"
do
if [[ "$arg" == "--uv" ]]
then
use_uv=1
break
fi
done
# read any command line flags to the webui.sh script
while getopts "f" flag > /dev/null 2>&1
do
@@ -59,16 +69,34 @@ do
fi
done
if ! "${PYTHON}" -c "import venv" &>/dev/null
if [[ "${use_uv}" -eq 1 ]]
then
echo "Error: python3-venv is not installed"
exit 1
if ! hash "uv" &>/dev/null
then
echo "Warning: uv is not installed globally"
use_uv=0
fi
fi
if [[ "${use_uv}" -eq 0 ]]
then
if ! "${PYTHON}" -c "import venv" &>/dev/null
then
echo "Error: python3-venv is not installed"
exit 1
fi
fi
if [[ ! -d "${venv_dir}" ]]
then
echo "Create python venv"
"${PYTHON}" -m venv "${venv_dir}"
if [[ "${use_uv}" -eq 1 ]]
then
echo "Create VENV: UV"
uv venv "${venv_dir}"
else
echo "Create VENV: VENV"
"${PYTHON}" -m venv "${venv_dir}"
fi
first_launch=1
fi