Windows: unblock the consumer install on clean and no-winget machines (#7549)
* Windows: unblock the consumer install on clean and no-winget machines Four independent things stop a clean Windows box today. git was a hard Exit-SetupFailure in setup.ps1, justified as required by pip for git+https:// deps and by npm. Neither holds on the consumer path: the unsloth-zoo git+https URL is only used under STUDIO_LOCAL_INSTALL, node is a pinned nodejs.org prebuilt that never touches system npm, and the frontend lockfile has no VCS dependencies. It stays fatal for --local, where it really is needed. Ensure-VCRedist was winget-only, so on hosts without winget (LTSC, Server, managed corporate images) it silently did nothing while the install reported success, and torch then failed to import on a missing VCRUNTIME140.dll. Adds a direct aka.ms/vs/17/release/vc_redist.<arch>.exe download with /quiet /norestart, accepting exit codes 0 and 3010. The redistributable stays required: it is the runtime the prebuilt llama-server and torch link against, not the MSVC compiler, which is already detection-only. Windows on ARM has no PyTorch at all. Measured with uv against download.pytorch.org/whl/cpu and PyPI for aarch64-pc-windows-msvc / cp313: torch, torchvision and torchaudio all resolve to nothing, wheels exist only for win_amd64 and the manylinux targets. The installer burned three uv retries on an unsatisfiable resolution and reported a bare 'Failed to install PyTorch (exit code 1)'. Now it says what is actually wrong and points at --no-torch, which works because llama.cpp does publish windows-arm64-cpu. install_node_prebuilt.py hit '[WinError 5] Access is denied' on os.replace of the freshly extracted directory during a FRESH install, which is a scanner or indexer holding handles for a moment. Retries only winerror 5, 32 and 145 with capped exponential backoff; any other OSError still raises immediately. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Give the ARM64 dead end a recovery that works for web installs The only remedy printed was .\install.ps1 --no-torch, but the documented path is irm | iex, where no file exists and flags cannot be forwarded. Name the env var the script already honours at line 145. * Windows on ARM: drop torchaudio, do not abort the install The fail-fast was based on a wrong premise. Counted against download.pytorch.org/whl/cpu: torch has 42 win_arm64 wheels and torchvision 60; only torchaudio has none. PyTorch has shipped Arm-native Windows builds since April 2025, so aborting blocked a platform that mostly works. Drop the one unsatisfiable pin instead. Decide from the interpreter uv will resolve for, not the PowerShell host: an x64 CPython under emulation gets working win_amd64 wheels on an ARM64 box, and powershell.exe inherits PROCESSOR_ARCHITECTURE from its parent. * Carry the ARM64 torchaudio omission into studio setup Dropping it from the first PyTorch command was not enough: install.ps1 then runs studio setup with SKIP_STUDIO_BASE=1 and setup.ps1 reinstalls the bare trio from the CPU index, so the ARM64 path still aborted. Apply the same interpreter-based test there. An unreadable platform keeps the full trio. * Build the torch spec list outside the verbose branch The ARM64 guard landed inside `if ($script:UnslothVerbose)`, so on the default path $_torchTrio was never assigned and the splat expanded to nothing: uv ran as `uv pip install --index-url ...` with no package, exit 2, straight to Exit-SetupFailure. That broke the ordinary Windows install. Hoist it above the branch and use substep, which prints on both paths. Realign the two parity guards to the splat form; they asserted the pre-refactor literal command and were the actual cause of the red parity legs. Both halves are still checked: the bounded list is built, and it reaches the install. * Tighten the comments on the Windows install path * Windows install: honour the ARM64 torchaudio skip everywhere and keep git for source builds Hoist the venv-interpreter platform probe above every torch branch in studio/setup.ps1 so the win_arm64 torchaudio omission applies to the ROCm, CPU and CUDA/custom paths. A pinned index whose leaf is not cpu routed an ARM64 host into the CUDA/custom branch, which still asked for torchaudio. Require git again when a llama.cpp source build is opted into up front (UNSLOTH_LLAMA_FORCE_COMPILE, UNSLOTH_LLAMA_PR / PR_FORCE, a non-upstream source). Those paths git clone in phase 4, so setup used to report git as not required, install the build toolchain, then fail at the clone. A local llama.cpp dir overrides them, and the automatic source fallback after a failed prebuilt download stays non-fatal. Also tighten the comments across the changed install paths. * Install the x64 VC++ runtime unconditionally in the direct-download fallback The winget branch always installs Microsoft.VCRedist.2015+.x64, but the direct-download fallback picked the package from PROCESSOR_ARCHITECTURE, which reports the architecture of the running PowerShell process rather than the interpreter that will load the DLLs. Find-CompatiblePython in install.ps1 selects an interpreter on version and non-Conda status alone, with no architecture predicate, so a native ARM64 shell can settle on an emulated x64 Python whose win_amd64 torch and prebuilt llama-server need the x64 runtime, while the fallback had just installed the ARM64-only package. Ensure-VCRedist also runs well before the venv exists, so the interpreter cannot be probed at that point. Microsoft ships the x64 redistributable as an Arm64X superset that carries both ARM64 and x64 binaries, so it is correct on both machines and the manual instruction printed on failure already pointed at it. * Windows on ARM: prefer an x64 Python interpreter An ARM64 host cannot complete the install with a native ARM64 interpreter. pyarrow, pulled in by unsloth -> datasets, has never published a win_arm64 wheel on any version, and neither has hf-transfer, a direct dependency. Both therefore fall back to a source build: pyarrow dies in scikit-build-core CMake configuration and hf-transfer dies in openssl-sys for want of perl, several minutes into a run that looked healthy. torch and torchvision are not the problem, they have win_arm64 wheels and install fine. Windows 11 on ARM runs x64 binaries under emulation and both packages ship win_amd64 wheels, so an x64 interpreter installs cleanly. Find-CompatiblePython accepted an interpreter on version and non-Conda status alone. It now ranks candidates by architecture on ARM64 hosts and returns an x64 one when present, asking each interpreter for its own sysconfig.get_platform() rather than guessing from its path. Host architecture comes from PROCESSOR_ARCHITEW6432 and OSArchitecture as well as PROCESSOR_ARCHITECTURE, which describes only the current process and reads AMD64 in an emulated shell. This is a preference, not a requirement. If only ARM64 is found, x64 is bootstrapped through winget --architecture x64 or the python.org fallback, and if neither works the installer names pyarrow and hf-transfer up front instead of failing later on a CMake or Rust error. The ARM64 torchaudio skip stays live for that path. Non-ARM hosts return on the first match exactly as before, with no extra interpreter probing. * Windows install: three correctness fixes on the ARM64 and git-less paths Ensure-VCRedist never reached its x64 download on an ARM64 machine that already had the arm64 redistributable: Test-VCRedistInstalled accepted System32\vcruntime140_1.dll regardless of architecture, and there that file can be the pure-ARM64 package. An ARM64 PE cannot load into an emulated x64 process, so the x64 Python this branch now prefers would have been left without a usable runtime. The x64 registry entry is the only x64-specific proof, and Microsoft registers Runtimes\{x86|x64|arm64} per architecture, so vc_redist.x64.exe still writes Runtimes\x64 on an ARM64 host and the check cannot loop. The DLL probe stays for x64 hosts. Phase 1 demanded git for any non-blank UNSLOTH_LLAMA_PR_FORCE, but the promotion that actually turns it into a source build requires a positive integer, so PR_FORCE=0 or a non-numeric value aborted a git-less consumer install for a build that never runs. Both sites now use the same predicate. The automatic fallback after a failed prebuilt llama.cpp download reached git clone with no git check anywhere in between, and Invoke-SetupCommand returns 0 for a command-not-found, so a git-less host did not stop there: it continued into an empty directory and reported a cmake configure failure instead. Git is now resolved where the source build is decided, with a last winget attempt, and a missing git degrades exactly like a missing cmake rather than aborting, since the opt-in source triggers already required git in Phase 1. Also tightened the comments across the changed Windows install code, keeping the reasons on the guards that prevent a specific failure. * Rank ARM64 Python candidates by minor version before architecture The x64 preference filtered the whole candidate list on architecture, which outranks the version preference the candidates were collected in. With UNSLOTH_PYTHON=3.12 on a Windows ARM64 box holding an ARM64 3.12 and an x64 3.13, it returned the x64 3.13: the explicit pin was silently broken, and because a x64 interpreter was found the caller never ran Install-X64Python to fetch an x64 3.12. With no pin it was worse still, since an x64 3.11 outranked a newer ARM64 3.13 and defeated the newest-first fallback. Walk $minors in order and take the x64 build of the best minor available, falling back to that minor's ARM64 build so the caller bootstraps x64 for the version actually requested. x64 still wins within a minor, and non-ARM hosts are untouched. * Windows install: see every registered Python, order git before the toolchain Find-CompatiblePython only ever probed `py -3.X`, which runs the launcher's preferred build for that minor. On an ARM64 box that is the native ARM64 interpreter, so a same-minor x64 install that is registered with the launcher but neither preferred nor on PATH never became a candidate. The x64 preference then lost to ARM64, and Install-X64Python re-downloaded an x64 CPython that was already on the machine; when that download is unavailable the install continues on ARM64 and source-builds pyarrow and hf-transfer, which publish no win_arm64 wheels. Enumerate `py -0p` on ARM64 hosts and probe each listed path. The `-3.12-64` suffix cannot be used for this: it has meant "not 32-bit" since 3.11 and does not distinguish arm64 from amd64. studio/setup.ps1 ran Ensure-BuildToolsForLlamaSourceBuild before checking git in Phase 4. That helper calls Exit-SetupFailure when Visual Studio Build Tools cannot be installed, so on a clean no-winget box the git degraded path added by this PR was unreachable and a standalone update aborted instead of finishing in limited mode; where winget does exist it spent a multi-GB Build Tools download on a clone that could never run. Check and install git first, skip the toolchain helper when git is still missing, and report the git branch before the cmake branch so the message names the real cause. _swap_into_place retried the forward rename for about 16 seconds but rolled back with a bare os.replace. A scanner holding the backup for the same WinError 5/32 then left no install_dir at all and stranded the working runtime in .old-*, and its exception replaced the original failure. The rollback now uses the same backoff and logs instead of masking the error it is recovering from. * Installer: use an already installed x64 Python on ARM64 when none can be downloaded Find-CompatiblePython ranks x64 within one minor and returns the native build when that minor is ARM64-only, leaving Install-X64Python to bootstrap x64. On an offline or winget-less box that bootstrap fails, and the retry went through the same resolver, so an x64 build of a lower-priority supported minor already on the machine was never picked up and setup continued on ARM64 Python, where pyarrow and hf-transfer have no wheels. Add an -X64Only mode that returns the best installed x64 interpreter or nothing, and call it as the last resort in Install-X64Python. The version-first preference is unchanged: x64 of the requested minor is still bootstrapped first. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Tighten comments in the Windows ARM64 installer changes * Setup: require Git for a source build behind an unbuilt local llama.cpp dir UNSLOTH_LOCAL_LLAMA_CPP_DIR only overrides the source-build opt-ins once the directory holds a reusable llama-server.exe. Pointing it at the canonical install location with nothing built there falls through to the normal install, so the Phase 1 gate now probes the same layout candidates as the Phase 4 reuse check before dropping the requirement. * Setup: require Git when UNSLOTH_LLAMA_TAG=master forces a source build * Tighten comments in the Windows installer changes * Setup: negotiate TLS 1.2 for the direct VC++ runtime download --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: danielhanchen <unslothai@gmail.com>
This commit is contained in:
parent
bd3972804d
commit
9bfa18cdb0
8 changed files with 791 additions and 35 deletions
|
|
@ -707,18 +707,55 @@ def existing_install_usable(install_dir: Path, host: HostInfo) -> bool:
|
|||
return npm_major is not None and npm_major >= NPM_MIN_MAJOR
|
||||
|
||||
|
||||
def _replace_with_retry(
|
||||
src: Path,
|
||||
dst: Path,
|
||||
*,
|
||||
attempts: int = 8,
|
||||
) -> None:
|
||||
"""os.replace, retried against transient Windows sharing violations.
|
||||
|
||||
A directory rename fails with WinError 5/32 while any process holds a handle inside
|
||||
it, and Defender or the indexer routinely does right after extraction (seen in CI on
|
||||
a fresh install, with no existing directory to conflict with). Handles clear in a
|
||||
second or two, so a bounded backoff turns the failure into a pause; other errors
|
||||
raise immediately rather than stalling on a real problem.
|
||||
"""
|
||||
delay = 0.25
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
os.replace(src, dst)
|
||||
return
|
||||
except OSError as exc:
|
||||
transient = os.name == "nt" and getattr(exc, "winerror", None) in (5, 32, 145)
|
||||
if not transient or attempt == attempts - 1:
|
||||
raise
|
||||
log(
|
||||
f"rename blocked ({exc.winerror}), retrying in {delay:.2f}s "
|
||||
f"-- a scanner is likely still holding the extracted files"
|
||||
)
|
||||
time.sleep(delay)
|
||||
delay = min(delay * 2, 4.0)
|
||||
|
||||
|
||||
def _swap_into_place(extracted_root: Path, install_dir: Path) -> None:
|
||||
"""Atomically replace install_dir with extracted_root (same filesystem)."""
|
||||
install_dir.parent.mkdir(parents = True, exist_ok = True)
|
||||
backup: Path | None = None
|
||||
if install_dir.exists():
|
||||
backup = install_dir.parent / f".{install_dir.name}.old-{os.getpid()}"
|
||||
os.replace(install_dir, backup)
|
||||
_replace_with_retry(install_dir, backup)
|
||||
try:
|
||||
os.replace(extracted_root, install_dir)
|
||||
_replace_with_retry(extracted_root, install_dir)
|
||||
except OSError:
|
||||
# The forward rename retries ~16s, ample time for a scanner to grab the backup too.
|
||||
# A plain os.replace would then raise over the original error and leave no
|
||||
# install_dir at all, so the rollback gets the same backoff and never masks it.
|
||||
if backup is not None and not install_dir.exists():
|
||||
os.replace(backup, install_dir)
|
||||
try:
|
||||
_replace_with_retry(backup, install_dir)
|
||||
except OSError as rollback_exc:
|
||||
log(f"could not restore the previous Node install from {backup}: {rollback_exc}")
|
||||
raise
|
||||
if backup is not None:
|
||||
shutil.rmtree(backup, ignore_errors = True)
|
||||
|
|
|
|||
182
studio/setup.ps1
182
studio/setup.ps1
|
|
@ -869,12 +869,22 @@ function Ensure-BuildToolsForLlamaSourceBuild {
|
|||
}
|
||||
}
|
||||
|
||||
# Detect the VC++ 2015-2022 Redistributable that the prebuilt llama-server and
|
||||
# PyTorch need (they link VCRUNTIME140_1.dll etc., which the Universal CRT lacks).
|
||||
# Signal is System32\vcruntime140_1.dll (VS 2019+), registry as fallback.
|
||||
# Machine arch: PROCESSOR_ARCHITECTURE describes this PROCESS, so an emulated x64 shell on
|
||||
# ARM64 reports AMD64; PROCESSOR_ARCHITEW6432 is ARM64 in exactly that case.
|
||||
function Get-HostMachineArch {
|
||||
$osArch = ""
|
||||
try { $osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } catch { }
|
||||
foreach ($s in @([string]$env:PROCESSOR_ARCHITEW6432, [string]$env:PROCESSOR_ARCHITECTURE, $osArch)) {
|
||||
if ($s.ToLowerInvariant() -eq "arm64") { return "arm64" }
|
||||
}
|
||||
return "other"
|
||||
}
|
||||
|
||||
# Detect the VC++ 2015-2022 Redistributable prebuilt llama-server and PyTorch need (they
|
||||
# link VCRUNTIME140_1.dll, absent from the Universal CRT). Registry first: Runtimes\x64 is
|
||||
# the only x64-specific proof; System32\vcruntime140_1.dll is arch-blind and on ARM64 may
|
||||
# be the ARM64-only package, unloadable under x64 emulation.
|
||||
function Test-VCRedistInstalled {
|
||||
$sys = $env:SystemRoot
|
||||
if ($sys -and (Test-Path (Join-Path $sys 'System32\vcruntime140_1.dll'))) { return $true }
|
||||
foreach ($k in @(
|
||||
'HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64',
|
||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64'
|
||||
|
|
@ -884,10 +894,14 @@ function Test-VCRedistInstalled {
|
|||
if ($r.Installed -eq 1 -and [int]$r.Major -ge 14 -and [int]$r.Minor -ge 20) { return $true }
|
||||
} catch { }
|
||||
}
|
||||
if ((Get-HostMachineArch) -eq "arm64") { return $false }
|
||||
$sys = $env:SystemRoot
|
||||
if ($sys -and (Test-Path (Join-Path $sys 'System32\vcruntime140_1.dll'))) { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
# Install the VC++ 2015-2022 runtime if missing (non-fatal; usually a no-op).
|
||||
# Install the VC++ 2015-2022 runtime if missing (non-fatal; usually a no-op). Unlike CMake
|
||||
# and Build Tools torch cannot import without it, and winget is absent on LTSC/Server images.
|
||||
function Ensure-VCRedist {
|
||||
if (Test-VCRedistInstalled) { step "vcredist" "present"; return }
|
||||
Write-Host "Microsoft Visual C++ Redistributable (2015-2022) is missing; the prebuilt llama.cpp and PyTorch need it. Installing the runtime..." -ForegroundColor Yellow
|
||||
|
|
@ -897,6 +911,45 @@ function Ensure-VCRedist {
|
|||
Refresh-Environment
|
||||
} catch { substep "VCRedist install failed: $($_.Exception.Message)" "Yellow" }
|
||||
}
|
||||
if (-not (Test-VCRedistInstalled)) {
|
||||
# Evergreen link; /quiet /norestart so it never blocks or reboots an unattended run.
|
||||
# Always the x64 package, deliberately: Microsoft ships it as the Arm64X superset of
|
||||
# both ARM64 and X64 binaries and documents it as the one for ARM64 devices, while
|
||||
# the arm64 package is ARM64-only (learn.microsoft.com/cpp/windows/latest-supported-vc-redist).
|
||||
# PROCESSOR_ARCHITECTURE is wrong twice here: it reports the process, and the runtime
|
||||
# must match the interpreter loading the DLLs, an emulated x64 Python not yet created.
|
||||
$url = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
$dst = Join-Path ([System.IO.Path]::GetTempPath()) "vc_redist.x64.exe"
|
||||
substep "winget unavailable or failed; downloading the runtime directly..."
|
||||
# Windows PowerShell 5.1 on an old image can carry a .NET default protocol set that
|
||||
# predates TLS 1.2, which aka.ms refuses -- exactly the no-winget host this fallback
|
||||
# exists for. SystemDefault (0) means "let the OS choose" and already covers TLS 1.2+,
|
||||
# so only an explicit legacy set is upgraded, and it is restored afterwards.
|
||||
$_prevProtocol = $null
|
||||
try {
|
||||
$_cur = [System.Net.ServicePointManager]::SecurityProtocol
|
||||
if ([int]$_cur -ne 0 -and ([int]$_cur -band [int][System.Net.SecurityProtocolType]::Tls12) -eq 0) {
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = $_cur -bor [System.Net.SecurityProtocolType]::Tls12
|
||||
$_prevProtocol = $_cur
|
||||
}
|
||||
} catch { $_prevProtocol = $null }
|
||||
try {
|
||||
Invoke-WebRequest -Uri $url -OutFile $dst -UseBasicParsing -TimeoutSec 300
|
||||
$p = Start-Process -FilePath $dst -ArgumentList '/quiet', '/norestart' -Wait -PassThru
|
||||
# 3010 = success, reboot required; usable either way.
|
||||
if ($p.ExitCode -notin @(0, 3010)) {
|
||||
substep "VC++ runtime installer exited $($p.ExitCode)" "Yellow"
|
||||
}
|
||||
Refresh-Environment
|
||||
} catch {
|
||||
substep "Direct VC++ runtime download failed: $($_.Exception.Message)" "Yellow"
|
||||
} finally {
|
||||
if ($null -ne $_prevProtocol) {
|
||||
try { [System.Net.ServicePointManager]::SecurityProtocol = $_prevProtocol } catch { }
|
||||
}
|
||||
Remove-Item -LiteralPath $dst -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
if (Test-VCRedistInstalled) { step "vcredist" "installed" }
|
||||
else {
|
||||
substep "Could not install the VC++ Redistributable automatically." "Yellow"
|
||||
|
|
@ -1650,11 +1703,42 @@ if ($LongPathsEnabled) {
|
|||
}
|
||||
|
||||
# ============================================
|
||||
# 1b. Git (required by pip for git+https:// deps and by npm)
|
||||
# 1b. Git (only required for --local / source installs)
|
||||
# ============================================
|
||||
# Was fatal as "required by pip and npm", but the consumer path uses neither: the
|
||||
# unsloth-zoo git+https URL is STUDIO_LOCAL_INSTALL only, node is a pinned prebuilt, and the
|
||||
# frontend lockfile has no VCS deps. Being fatal blocked clean no-winget Windows boxes.
|
||||
$HasGit = $null -ne (Get-Command git -ErrorAction SilentlyContinue)
|
||||
if (-not $HasGit) {
|
||||
Write-Host "Git not found -- installing via winget..." -ForegroundColor Yellow
|
||||
# Fatal only where git is used: --local and the opt-in llama.cpp source build. A local
|
||||
# llama.cpp dir overrides those opt-ins, but only once it holds a reusable binary:
|
||||
# pointing at the canonical install location with nothing built there falls through to
|
||||
# the normal install, so an explicit source build still needs git. The automatic
|
||||
# fallback after a failed prebuilt download is not knowable here; Phase 4 handles it.
|
||||
$gitNeeded = ($env:STUDIO_LOCAL_INSTALL -eq '1')
|
||||
$_localLlamaDir = if ($env:UNSLOTH_LOCAL_LLAMA_CPP_DIR) { $env:UNSLOTH_LOCAL_LLAMA_CPP_DIR.Trim() } else { "" }
|
||||
$_localLlamaBuilt = $false
|
||||
if ($_localLlamaDir) {
|
||||
# Same layout candidates as the reuse check in Phase 4.
|
||||
foreach ($_c in @("llama-server.exe", "build\bin\llama-server.exe", "build\bin\Release\llama-server.exe")) {
|
||||
if (Test-Path -LiteralPath (Join-Path $_localLlamaDir $_c)) { $_localLlamaBuilt = $true; break }
|
||||
}
|
||||
}
|
||||
if (-not $_localLlamaBuilt) {
|
||||
$_prForce = if ($env:UNSLOTH_LLAMA_PR_FORCE) { $env:UNSLOTH_LLAMA_PR_FORCE.Trim() } else { $DefaultLlamaPrForce }
|
||||
$_llamaSrc = $DefaultLlamaSource -replace '\.git$', ''
|
||||
# Same tag resolution as Phase 4. "master" is a branch, never a release, so the
|
||||
# prebuilt lookup always misses and Phase 4 rebuilds it from source.
|
||||
$_llamaTag = if ($env:UNSLOTH_LLAMA_TAG) { $env:UNSLOTH_LLAMA_TAG } else { $DefaultLlamaTag }
|
||||
if ($_llamaTag -eq "master") { $gitNeeded = $true }
|
||||
if ($env:UNSLOTH_LLAMA_FORCE_COMPILE -eq '1') { $gitNeeded = $true }
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:UNSLOTH_LLAMA_PR)) { $gitNeeded = $true }
|
||||
# Same positive-integer predicate as the PR_FORCE promotion below: 0 or non-numeric
|
||||
# never forces a source build, so it must not demand git.
|
||||
if ($_prForce -match '^\d+$' -and [int]$_prForce -gt 0) { $gitNeeded = $true }
|
||||
if ($_llamaSrc -ne "https://github.com/ggml-org/llama.cpp") { $gitNeeded = $true }
|
||||
}
|
||||
Write-Host "Git not found -- attempting install via winget..." -ForegroundColor Yellow
|
||||
$HasWinget = $null -ne (Get-Command winget -ErrorAction SilentlyContinue)
|
||||
if ($HasWinget) {
|
||||
try {
|
||||
|
|
@ -1664,11 +1748,18 @@ if (-not $HasGit) {
|
|||
} catch { }
|
||||
}
|
||||
if (-not $HasGit) {
|
||||
Write-Host "[ERROR] Git is required but could not be installed automatically." -ForegroundColor Red
|
||||
Write-Host " Install Git from https://git-scm.com/download/win and re-run." -ForegroundColor Red
|
||||
Exit-SetupFailure "Git is required but could not be installed automatically"
|
||||
if ($gitNeeded) {
|
||||
Write-Host "[ERROR] Git is required for --local and llama.cpp source-build installs but could not be installed." -ForegroundColor Red
|
||||
Write-Host " --local clones unsloth-zoo, and a source build clones llama.cpp." -ForegroundColor Red
|
||||
Write-Host " Install Git from https://git-scm.com/download/win and re-run." -ForegroundColor Red
|
||||
Exit-SetupFailure "Git is required for --local / source-build installs but could not be installed"
|
||||
}
|
||||
step "git" "not found (not required)" "Yellow"
|
||||
substep "Unsloth installs prebuilt binaries and wheels, so git is not needed."
|
||||
substep "Install it only for --local/source installs: https://git-scm.com/download/win"
|
||||
} else {
|
||||
step "git" "$(git --version)"
|
||||
}
|
||||
step "git" "$(git --version)"
|
||||
} else {
|
||||
step "git" "$(git --version)"
|
||||
}
|
||||
|
|
@ -3275,18 +3366,32 @@ $PyTorchWhlBase = if ($env:UNSLOTH_PYTORCH_MIRROR) { $env:UNSLOTH_PYTORCH_MIRROR
|
|||
$TorchInstallIndexUrl = if ($ROCmIndexUrl) { "$PyTorchWhlBase/cpu" } elseif ($PinnedTorchIndexUrl) { $PinnedTorchIndexUrl } else { "$PyTorchWhlBase/$CuTag" }
|
||||
|
||||
if (-not $NoTorchMode) {
|
||||
# Windows on ARM has win_arm64 torch and torchvision wheels but no torchaudio on any index,
|
||||
# so every branch below drops it. Ask the interpreter uv resolves for, not
|
||||
# PROCESSOR_ARCHITECTURE, which describes the host process. Inside the no-torch guard
|
||||
# because all three uses are, and no-torch installs nothing to skip.
|
||||
$_setupPlatform = ""
|
||||
try {
|
||||
$_setupPlatform = (& python -c "import sysconfig; print(sysconfig.get_platform())" 2>$null | Out-String).Trim().ToLowerInvariant()
|
||||
} catch { $_setupPlatform = "" }
|
||||
$WinArm64NoAudio = ($_setupPlatform -eq "win-arm64")
|
||||
if ($WinArm64NoAudio) { substep "windows on arm: skipping torchaudio (no win_arm64 wheel upstream)" }
|
||||
|
||||
$ROCmCpuFallback = $false
|
||||
if ($ROCmIndexUrl) {
|
||||
substep "installing PyTorch (AMD ROCm, $ROCmGfxArch)..."
|
||||
if ($ROCmTorchSpec -ne "torch") {
|
||||
substep " enforcing $ROCmTorchSpec $ROCmVisionSpec $ROCmAudioSpec (known _grouped_mm bug in older wheels)" "Cyan"
|
||||
}
|
||||
# Built above the verbose branch: a splat assigned inside it is unset on the other.
|
||||
$_rocmTrio = @($ROCmTorchSpec, $ROCmVisionSpec, $ROCmAudioSpec)
|
||||
if ($WinArm64NoAudio) { $_rocmTrio = @($ROCmTorchSpec, $ROCmVisionSpec) }
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install $ROCmTorchSpec $ROCmVisionSpec $ROCmAudioSpec --force-reinstall --index-url $ROCmIndexUrl | ForEach-Object { Redact-InstallOutput "$_" } | Out-Host
|
||||
Fast-Install @_rocmTrio --force-reinstall --index-url $ROCmIndexUrl | ForEach-Object { Redact-InstallOutput "$_" } | Out-Host
|
||||
$torchInstallExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install $ROCmTorchSpec $ROCmVisionSpec $ROCmAudioSpec --force-reinstall --index-url $ROCmIndexUrl | Out-String
|
||||
$output = Fast-Install @_rocmTrio --force-reinstall --index-url $ROCmIndexUrl | Out-String
|
||||
$torchInstallExit = $LASTEXITCODE
|
||||
}
|
||||
if ($torchInstallExit -ne 0) {
|
||||
|
|
@ -3322,12 +3427,14 @@ if (-not $ROCmIndexUrl -and ($CuTag -eq "cpu" -or $ROCmCpuFallback)) {
|
|||
$cpuVisionSpec = "torchvision>=0.19,<0.27.0"
|
||||
$cpuAudioSpec = "torchaudio>=2.4,<2.12.0"
|
||||
}
|
||||
$_torchTrio = @($cpuTorchSpec, $cpuVisionSpec, $cpuAudioSpec)
|
||||
if ($WinArm64NoAudio) { $_torchTrio = @($cpuTorchSpec, $cpuVisionSpec) }
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install $cpuTorchSpec $cpuVisionSpec $cpuAudioSpec @cpuForce --index-url $TorchInstallIndexUrl | ForEach-Object { Redact-InstallOutput "$_" } | Out-Host
|
||||
Fast-Install @_torchTrio @cpuForce --index-url $TorchInstallIndexUrl | ForEach-Object { Redact-InstallOutput "$_" } | Out-Host
|
||||
$torchInstallExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install $cpuTorchSpec $cpuVisionSpec $cpuAudioSpec @cpuForce --index-url $TorchInstallIndexUrl | Out-String
|
||||
$output = Fast-Install @_torchTrio @cpuForce --index-url $TorchInstallIndexUrl | Out-String
|
||||
$torchInstallExit = $LASTEXITCODE
|
||||
}
|
||||
if ($torchInstallExit -ne 0) {
|
||||
|
|
@ -3354,12 +3461,16 @@ if (-not $ROCmIndexUrl -and ($CuTag -eq "cpu" -or $ROCmCpuFallback)) {
|
|||
$cudaVisionSpec = "torchvision>=0.19,<0.26.0"
|
||||
$cudaAudioSpec = "torchaudio>=2.4,<2.11.0"
|
||||
}
|
||||
# A custom pin whose leaf is not cpu (a corporate /simple mirror) lands an ARM64 host
|
||||
# here, so this branch drops torchaudio too.
|
||||
$_cudaTrio = @($cudaTorchSpec, $cudaVisionSpec, $cudaAudioSpec)
|
||||
if ($WinArm64NoAudio) { $_cudaTrio = @($cudaTorchSpec, $cudaVisionSpec) }
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install $cudaTorchSpec $cudaVisionSpec $cudaAudioSpec @cudaForce --index-url $TorchInstallIndexUrl | ForEach-Object { Redact-InstallOutput "$_" } | Out-Host
|
||||
Fast-Install @_cudaTrio @cudaForce --index-url $TorchInstallIndexUrl | ForEach-Object { Redact-InstallOutput "$_" } | Out-Host
|
||||
$torchInstallExit = $LASTEXITCODE
|
||||
$output = ""
|
||||
} else {
|
||||
$output = Fast-Install $cudaTorchSpec $cudaVisionSpec $cudaAudioSpec @cudaForce --index-url $TorchInstallIndexUrl | Out-String
|
||||
$output = Fast-Install @_cudaTrio @cudaForce --index-url $TorchInstallIndexUrl | Out-String
|
||||
$torchInstallExit = $LASTEXITCODE
|
||||
}
|
||||
if ($torchInstallExit -ne 0) {
|
||||
|
|
@ -4048,6 +4159,7 @@ $BuildDir = Join-Path $LlamaCppDir "build"
|
|||
$LlamaServerBin = Join-Path $BuildDir "bin\Release\llama-server.exe"
|
||||
|
||||
$HasCmakeForBuild = $null -ne (Get-Command cmake -ErrorAction SilentlyContinue)
|
||||
$HasGitForBuild = $null -ne (Get-Command git -ErrorAction SilentlyContinue)
|
||||
|
||||
# Check if existing llama-server matches current GPU mode. A CUDA-built binary
|
||||
# on a now-CPU-only machine (or vice versa) needs to be rebuilt.
|
||||
|
|
@ -4073,9 +4185,27 @@ if (Test-Path -LiteralPath $LlamaServerBin) {
|
|||
$WillBuildLlamaFromSource = $NeedLlamaSourceBuild -and `
|
||||
-not ((Test-Path -LiteralPath $LlamaServerBin) -and -not $NeedRebuild -and $RequestedLlamaTag -ne "master")
|
||||
if ($WillBuildLlamaFromSource) {
|
||||
Ensure-BuildToolsForLlamaSourceBuild
|
||||
# refresh so the chain below sees a newly installed cmake
|
||||
$HasCmakeForBuild = $null -ne (Get-Command cmake -ErrorAction SilentlyContinue)
|
||||
if (-not $HasGitForBuild) {
|
||||
# Phase 1 keeps git optional, so only the automatic fallback after a failed prebuilt
|
||||
# download arrives here without it. Last chance to install: Invoke-SetupCommand
|
||||
# returns 0 for command-not-found, so a git-less clone misreports as a cmake failure.
|
||||
if ($null -ne (Get-Command winget -ErrorAction SilentlyContinue)) {
|
||||
try {
|
||||
Invoke-SetupCommand { winget install Git.Git --source winget --accept-package-agreements --accept-source-agreements } | Out-Null
|
||||
Refresh-Environment
|
||||
} catch { }
|
||||
}
|
||||
$HasGitForBuild = $null -ne (Get-Command git -ErrorAction SilentlyContinue)
|
||||
}
|
||||
# Git first, then the toolchain: Ensure-BuildToolsForLlamaSourceBuild exits setup when
|
||||
# Build Tools cannot be installed, so running it first made the degraded path below
|
||||
# unreachable on a no-winget box, and elsewhere spent a multi-GB download on a clone
|
||||
# that cannot happen.
|
||||
if ($HasGitForBuild) {
|
||||
Ensure-BuildToolsForLlamaSourceBuild
|
||||
# refresh so the chain below sees a newly installed cmake
|
||||
$HasCmakeForBuild = $null -ne (Get-Command cmake -ErrorAction SilentlyContinue)
|
||||
}
|
||||
}
|
||||
|
||||
if ($LocalLlamaCppLinked) {
|
||||
|
|
@ -4093,6 +4223,16 @@ if ($LocalLlamaCppLinked) {
|
|||
# up new model architecture support (e.g. Gemma 4).
|
||||
Write-Host ""
|
||||
step "llama.cpp" "already built"
|
||||
} elseif (-not $HasGitForBuild) {
|
||||
# Before cmake: the toolchain install is skipped without git, so cmake may be missing
|
||||
# purely as a consequence. Degrade rather than abort; the opt-in source triggers already
|
||||
# required git in Phase 1, so only the automatic fallback lands here.
|
||||
Write-Host ""
|
||||
step "llama.cpp" "build skipped (git not available)" "Yellow"
|
||||
substep "The prebuilt download failed and a source build clones llama.cpp." "Yellow"
|
||||
substep "GGUF inference and export will not be available." "Yellow"
|
||||
substep "Install Git from https://git-scm.com/download/win and re-run setup." "Yellow"
|
||||
$script:LlamaCppDegraded = $true
|
||||
} elseif (-not $HasCmakeForBuild) {
|
||||
Write-Host ""
|
||||
if (-not $HasNvidiaSmi) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue