| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | param( |
| | [switch]$AutoYes, |
| | [switch]$ForceSa2Source |
| | ) |
| |
|
| | function Write-Section($t){ Write-Host "`n=== $t ===" -ForegroundColor Cyan } |
| | function Ask-YesNo($q){ |
| | if($AutoYes){ return $true } |
| | $r = Read-Host "$q [y/N]"; return $r -match '^(?i:y|yes)$' |
| | } |
| |
|
| | function Get-Python(){ |
| | $cands = @('python','py -3','py') |
| | foreach($p in $cands){ try{ $v = & $p -c "import sys;print(sys.executable)" 2>$null; if($LASTEXITCODE -eq 0 -and $v){ return @{ exe=$p; path=$v.Trim() } } }catch{} } |
| | return $null |
| | } |
| |
|
| | function Py-Exec($pyExe, $code){ |
| | |
| | $tmp = [System.IO.Path]::GetTempFileName() |
| | $pyf = [System.IO.Path]::ChangeExtension($tmp, '.py') |
| | Set-Content -Path $pyf -Value $code -Encoding UTF8 |
| | try { $out = & $pyExe $pyf } finally { Remove-Item -ErrorAction SilentlyContinue $tmp, $pyf } |
| | return $out |
| | } |
| |
|
| | function Invoke-Quiet($file, $argList, $label){ |
| | $logOut = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.out.log') |
| | $logErr = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), '.err.log') |
| | try { |
| | if([string]::IsNullOrWhiteSpace($argList)){ throw "ArgumentList is empty for $file" } |
| | $p = Start-Process -FilePath $file -ArgumentList $argList -NoNewWindow -PassThru ` |
| | -RedirectStandardOutput $logOut -RedirectStandardError $logErr |
| | $spinner = @('|','/','-','\') |
| | $i = 0 |
| | while(-not $p.HasExited){ |
| | Write-Host -NoNewline ("`r{0} {1}" -f $spinner[$i % $spinner.Count], $label) |
| | Start-Sleep -Milliseconds 150 |
| | $i++ |
| | } |
| | try { $p.Refresh() } catch {} |
| | $exitCode = 1 |
| | try { $exitCode = [int]$p.ExitCode } catch { $exitCode = 1 } |
| | if($exitCode -eq 0){ |
| | Write-Host ("`r{0} ... done " -f $label) -ForegroundColor Green |
| | } else { |
| | Write-Warning ("Failed: {0} (exit {1})" -f $label, $exitCode) |
| | Write-Host "---- build tail ----" -ForegroundColor DarkYellow |
| | if(Test-Path $logOut){ Get-Content $logOut -Tail 40 | ForEach-Object { Write-Host $_ -ForegroundColor DarkYellow } } |
| | if(Test-Path $logErr){ Get-Content $logErr -Tail 40 | ForEach-Object { Write-Host $_ -ForegroundColor DarkYellow } } |
| | Write-Host "--------------------" -ForegroundColor DarkYellow |
| | } |
| | return ($exitCode -eq 0) |
| | } finally { |
| | if(Test-Path $logOut){ Remove-Item -ErrorAction SilentlyContinue $logOut } |
| | if(Test-Path $logErr){ Remove-Item -ErrorAction SilentlyContinue $logErr } |
| | } |
| | } |
| |
|
| | function Get-TorchInfo($pyExe){ |
| | $code = @' |
| | try: |
| | import torch |
| | cuda = getattr(torch.version, "cuda", None) |
| | is_cuda = torch.cuda.is_available() |
| | name = torch.cuda.get_device_name(0) if is_cuda else "" |
| | cc = ".".join(map(str, torch.cuda.get_device_capability(0))) if is_cuda else "" |
| | print("|".join([torch.__version__, str(cuda or ""), "1" if is_cuda else "0", name.replace("|"," "), cc])) |
| | except Exception: |
| | print("") |
| | '@ |
| | $out = Py-Exec $pyExe $code |
| | if(-not $out){ return @{ has_torch=$false } } |
| | $p = $out -split '\|' |
| | if($p.Length -lt 3){ return @{ has_torch=$false } } |
| | return @{ has_torch=$true; torch=$p[0]; cuda=$p[1]; is_cuda=($p[2] -eq '1'); device_name=($p[3] | ForEach-Object { $_ }); cc=($p[4] | ForEach-Object { $_ }) } |
| | } |
| |
|
| | function Get-SageVersion($pyExe){ |
| | $code = @' |
| | try: |
| | import importlib, importlib.util |
| | mod = None |
| | for name in ("SageAttention","sageattention"): |
| | try: |
| | if importlib.util.find_spec(name) is not None: |
| | mod = name; break |
| | except Exception: |
| | pass |
| | if not mod: |
| | print("") |
| | else: |
| | try: |
| | import importlib.metadata as md |
| | ver = md.version(mod) |
| | except Exception: |
| | ver = "" |
| | print(f"{mod}|{ver}") |
| | except Exception: |
| | print("") |
| | '@ |
| | $out = Py-Exec $pyExe $code |
| | $p = $out -split '\|' |
| | return @{ module=($p[0]); version=($p[1]) } |
| | } |
| |
|
| | function Test-ShadowFile(){ |
| | |
| | $roots = @((Get-Location).Path) |
| | |
| | $d = Get-Item . |
| | for($i=0;$i -lt 3;$i++){ $d = $d.PSParentPath; if(-not $d){ break }; $roots += $d } |
| | foreach($r in $roots){ $f = Join-Path $r 'sageattention.py'; if(Test-Path $f){ return $f } } |
| | return $null |
| | } |
| |
|
| | Write-Section "Python" |
| | $py = Get-Python |
| | if(-not $py){ Write-Error "Python not found on PATH."; exit 1 } |
| | Write-Host ("Using Python: {0} ({1})" -f $py.exe, $py.path) |
| |
|
| | Write-Section "Torch / CUDA / GPU" |
| | $ti = Get-TorchInfo $py.path |
| | if(-not $ti.has_torch){ Write-Warning "PyTorch not found: $($ti.err)" } else { |
| | $ccdisp = if($ti.cc){ "sm_{0}" -f ($ti.cc -replace '\.','') } else { "-" } |
| | Write-Host ("torch {0}, cuda {1}, cuda_available={2}, gpu='{3}', cc={4}" -f $ti.torch, $ti.cuda, $ti.is_cuda, $ti.device_name, $ccdisp) |
| | } |
| |
|
| | Write-Section "SageAttention" |
| | $target = 'SA2 (Attn2++)' |
| | Write-Host ("Build target: {0}" -f $target) -ForegroundColor Green |
| | $sv = Get-SageVersion $py.path |
| | if($sv.module){ |
| | $svver = if($null -ne $sv.version -and $sv.version -ne ''){ $sv.version } else { 'unknown' } |
| | Write-Host ("found module: {0} version: {1}" -f $sv.module, $svver) |
| | } else { |
| | Write-Host "not installed" |
| | } |
| |
|
| | $shadow = Test-ShadowFile |
| | if($shadow){ Write-Warning "Local file shadows package: $shadow"; if(Ask-YesNo "Rename to sageattention.py.disabled now?"){ |
| | Rename-Item -Path $shadow -NewName 'sageattention.py.disabled' -Force |
| | Write-Host "Renamed." |
| | } |
| | } |
| |
|
| | $needInstall = $false |
| | $wantSA3 = $false |
| | |
| | $isWindows = ($env:OS -eq 'Windows_NT') |
| | |
| | $sa3check = @' |
| | import importlib.util |
| | print(importlib.util.find_spec("sageattn3") is not None) |
| | '@ |
| | $sa3present = $false |
| | if($wantSA3){ |
| | try { |
| | $sa3present = ((Py-Exec $py.path $sa3check).Trim() -eq 'True') |
| | } catch { $sa3present = $false } |
| | } |
| |
|
| | if($wantSA3){ |
| | $needInstall = -not $sa3present |
| | } else { |
| | if(-not $sv.module){ $needInstall = $true } |
| | else { try{ $ver=[Version]($sv.version -replace '[^0-9\.]',''); if($ver.Major -lt 2 -or ($ver.Major -eq 2 -and $ver.Minor -lt 2)){ $needInstall=$true } }catch{ $needInstall=$true } } |
| | } |
| |
|
| | if(-not $needInstall){ |
| | Write-Host ("SageAttention present (target {0}) — nothing to do." -f $target) -ForegroundColor Green; exit 0 |
| | } |
| |
|
| | if($wantSA3){ |
| | if($isWindows){ |
| | Write-Warning "SageAttention 3 (Blackwell) is not supported on Windows currently. Falling back to SageAttention 2.2.x." |
| | $wantSA3 = $false |
| | } |
| | } |
| |
|
| | if($wantSA3){ |
| | if(-not (Ask-YesNo "Install SageAttention SA3 (Blackwell) from source now?")){ Write-Host "Aborted by user."; exit 0 } |
| | Write-Section "Installing (from source)" |
| | $null = Invoke-Quiet $py.path "-m pip install -U pip setuptools wheel" "Installing SageAttention SA3, please wait a few minutes" |
| | |
| | Write-Section "Toolchain check" |
| | $hasCL = ($null -ne (Get-Command cl.exe -ErrorAction SilentlyContinue)) |
| | $hasNVCC = ($null -ne (Get-Command nvcc.exe -ErrorAction SilentlyContinue)) |
| | if(-not $hasCL -or -not $hasNVCC){ |
| | Write-Warning "MSVC cl or CUDA nvcc not found. Install MSVC Build Tools 2022 and CUDA Toolkit matching your torch (CUDA $($ti.cuda))." |
| | exit 1 |
| | } |
| | |
| | if($ti.is_cuda -and $ti.cc){ $env:TORCH_CUDA_ARCH_LIST = $ti.cc } |
| | Write-Section "Building SA3 from source" |
| | $null = Invoke-Quiet $py.path "-m pip install -U packaging cmake ninja" "toolchain python deps" |
| | $sa3built = $false |
| | |
| | $env:GIT_TERMINAL_PROMPT = "0" |
| | if($sa3present){ $null = Invoke-Quiet $py.path "-m pip uninstall -y sageattn3" "uninstall SA3 (old)" } |
| | if(Invoke-Quiet $py.path "-m pip install -U --force-reinstall --no-build-isolation --no-cache-dir git+https://github.com/thu-ml/SageAttention@main#subdirectory=sageattention3_blackwell" "install SA3 from git subdir (main)"){ $sa3built = $true } |
| | if(-not $sa3built){ |
| | |
| | $tags = @('v2.2.1','v2.2.0','v2.2') |
| | foreach($t in $tags){ |
| | if(Invoke-Quiet $py.path ("-m pip install -U --force-reinstall --no-build-isolation --no-cache-dir git+https://github.com/thu-ml/SageAttention@{0}#subdirectory=sageattention3_blackwell" -f $t) ("install SA3 from git subdir: {0}" -f $t)){ $sa3built = $true; break } |
| | } |
| | } |
| | try { $sa3present = ((Py-Exec $py.path $sa3check).Trim() -eq 'True') } catch { $sa3present = $false } |
| | if(-not $sa3present){ |
| | Write-Warning "SA3 package not importable after installation. Possible env mismatch or build skipped." |
| | |
| | $null = Invoke-Quiet $py.path "-m pip show -f sageattn3" "pip show sageattn3" |
| | $diag = @' |
| | import sys, site, importlib.util |
| | print("py=", sys.executable) |
| | paths = [] |
| | try: |
| | paths += site.getsitepackages() |
| | except Exception: |
| | pass |
| | try: |
| | paths.append(site.getusersitepackages()) |
| | except Exception: |
| | pass |
| | print("site=", ";".join(paths)) |
| | spec = importlib.util.find_spec("sageattn3") |
| | print("spec=", None if spec is None else (spec.origin or str(spec.submodule_search_locations))) |
| | '@ |
| | $dout = Py-Exec $py.path $diag |
| | if($dout){ Write-Host $dout -ForegroundColor DarkYellow } |
| | } |
| | |
| | } else { |
| | if(-not (Ask-YesNo "Install/upgrade SageAttention to 2.2.x now?")){ Write-Host "Aborted by user."; exit 0 } |
| | Write-Section "Installing (wheel if available)" |
| | $null = Invoke-Quiet $py.path "-m pip install -U pip setuptools wheel" "Installing SageAttention 2.2.x, please wait a few minutes" |
| | |
| | $null = Invoke-Quiet $py.path "-m pip uninstall -y SageAttention" "uninstall legacy SageAttention (if any)" |
| | $null = Invoke-Quiet $py.path "-m pip uninstall -y sageattention" "uninstall legacy sageattention (if any)" |
| | $wheelOk = Invoke-Quiet $py.path "-m pip install -U --no-cache-dir sageattention>=2.2,<3" "pip install sageattention 2.2.x (wheel)" |
| | |
| | $sv2 = Get-SageVersion $py.path |
| | $wheelHas22 = $false |
| | if($sv2.module -and $sv2.version){ try{ $v=[Version]($sv2.version -replace '[^0-9\.]',''); if($v.Major -gt 2 -or ($v.Major -eq 2 -and $v.Minor -ge 2)){ $wheelHas22=$true } }catch{} |
| | } |
| | if($wheelOk -and -not $wheelHas22){ Write-Warning "Wheel installation did not provide SageAttention >= 2.2. Falling back to source build."; $wheelOk=$false } |
| | } |
| |
|
| | if(-not $wantSA3 -and -not $wheelOk){ |
| | Write-Warning "Wheel install failed - will try source build." |
| | |
| | $arch = '' |
| | if($ti.is_cuda -and $ti.cc){ $arch = $ti.cc } |
| | if($arch){ $env:TORCH_CUDA_ARCH_LIST = $arch } |
| | |
| | Write-Section "Toolchain check" |
| | $hasCL = ($null -ne (Get-Command cl.exe -ErrorAction SilentlyContinue)) |
| | $hasNVCC = ($null -ne (Get-Command nvcc.exe -ErrorAction SilentlyContinue)) |
| | if(-not $hasCL -or -not $hasNVCC){ |
| | Write-Warning "MSVC cl or CUDA nvcc not found. Install MSVC Build Tools 2022 and CUDA Toolkit matching your torch (CUDA $($ti.cuda))." |
| | exit 1 |
| | } |
| | if($isWindows -and -not $ForceSa2Source){ |
| | Write-Warning "Attempting SageAttention 2.x source build on Windows (experimental upstream support)." |
| | } |
| | Write-Section "Building from source" |
| | $null = Invoke-Quiet $py.path "-m pip install -U packaging cmake ninja" "toolchain python deps" |
| | |
| | $built = $false |
| | $urls = @( |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/heads/main.zip', |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/tags/v2.2.1.zip', |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/tags/v2.2.0.zip', |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/tags/v2.2.zip' |
| | ) |
| | foreach($u in $urls){ |
| | if(Invoke-Quiet $py.path "-m pip install --no-build-isolation --no-cache-dir `"$u`"" ("build from archive: {0}" -f $u)) { $built = $true; break } |
| | } |
| | if(-not $built){ |
| | Write-Warning "Tag archive not available; trying git main (noninteractive)." |
| | $env:GIT_TERMINAL_PROMPT = "0" |
| | $null = Invoke-Quiet $py.path "-m pip install --no-build-isolation --no-cache-dir git+https://github.com/thu-ml/SageAttention@main" "build from git main" |
| | } |
| | |
| | } |
| |
|
| | |
| | if($wantSA3 -and -not $sa3present){ |
| | Write-Warning "Falling back to SageAttention 2.2.x (wheel/source)." |
| | Write-Section "Installing SA2 (fallback)" |
| | $null = Invoke-Quiet $py.path "-m pip install -U pip setuptools wheel" "Installing SageAttention 2.2.x, please wait a few minutes" |
| | $wheelOk = Invoke-Quiet $py.path "-m pip install -U --no-cache-dir sageattention>=2.2,<3" "pip install sageattention 2.2.x (wheel)" |
| | if(-not $wheelOk){ |
| | Write-Section "Building SA2 from source" |
| | $null = Invoke-Quiet $py.path "-m pip install -U packaging cmake ninja" "toolchain python deps" |
| | $built = $false |
| | $urls = @( |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/heads/main.zip', |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/tags/v2.2.1.zip', |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/tags/v2.2.0.zip', |
| | 'https://github.com/thu-ml/SageAttention/archive/refs/tags/v2.2.zip' |
| | ) |
| | foreach($u in $urls){ if(Invoke-Quiet $py.path "-m pip install --no-build-isolation --no-cache-dir `"$u`"" ("build from archive: {0}" -f $u)) { $built = $true; break } } |
| | if(-not $built){ |
| | $env:GIT_TERMINAL_PROMPT = "0" |
| | $null = Invoke-Quiet $py.path "-m pip install --no-build-isolation --no-cache-dir git+https://github.com/thu-ml/SageAttention@main" "build from git main" |
| | } |
| | } |
| | } |
| |
|
| | Write-Section "Validation" |
| | $val = @' |
| | try: |
| | import importlib.util, torch |
| | S = None |
| | try: |
| | import SageAttention as S |
| | except Exception: |
| | try: |
| | import sageattention as S |
| | except Exception: |
| | S = None |
| | sa_ok = False |
| | if S is not None: |
| | sa_ok = ( |
| | hasattr(S, 'sageattn_qk_int8_pv_fp16_cuda') or |
| | hasattr(S, 'sageattn_qk_int8_pv_fp16_cuda_fp16') |
| | ) |
| | if sa_ok: |
| | print(f"OK: True torch {torch.__version__}") |
| | else: |
| | print(f"FAIL: torch {torch.__version__}") |
| | except Exception as e: |
| | print("ERR:", str(e)) |
| | '@ |
| | $out = Py-Exec $py.path $val |
| | if($out -match '^OK:'){ Write-Host $out -ForegroundColor Green; Write-Host "Done." -ForegroundColor Green } |
| | else { Write-Warning $out; Write-Warning "SageAttention not available. ComfyUI will fall back to stock attention (slower)." } |
| |
|