From 29baafe19b460715891942e6d5d0f504cc2a2e87 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 16 Jan 2026 21:30:39 -0800 Subject: [PATCH 01/14] Merge pull request #1189 from ionite34/old-stuff-notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add warnings for legacy Python in InvokeAI and NVIDIA driver versions… (cherry picked from commit 0c792e7cf8df4526951aa2cd3bc96b4a318f1df0) # Conflicts: # CHANGELOG.md --- CHANGELOG.md | 13 +++++ .../Views/CivitAiBrowserPage.axaml | 9 ++-- .../Helper/HardwareInfo/HardwareHelper.cs | 35 ++++++++++++ .../Models/Packages/AiToolkit.cs | 5 +- .../Models/Packages/ComfyUI.cs | 53 +++++++++++++++++++ .../Models/Packages/FluxGym.cs | 3 +- .../Models/Packages/InvokeAI.cs | 43 +++++++++++++++ 7 files changed, 151 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b96203..26f29d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,20 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +<<<<<<< HEAD ## v2.15.5 +======= +## v2.16.0-dev.2 +### Added +- Added NVIDIA driver version warning when launching ComfyUI with CUDA 13.0 (cu130) and driver versions below 580.x +- Added legacy Python warning when launching InvokeAI installations using Python 3.10.11 +### Changed +- Disabled update checking for legacy InvokeAI installations using Python 3.10.11 +### Fixed +- Hide rating stars in the Civitai browser page if no rating is available + +## v2.16.0-dev.1 +>>>>>>> 0c792e7c (Merge pull request #1189 from ionite34/old-stuff-notifications) ### Added - Added new package - [Wan2GP](https://github.com/deepbeepmeep/Wan2GP) - Added [Stable Diffusion WebUI Forge - Neo](https://github.com/Haoming02/sd-webui-forge-classic/tree/neo) as a separate package for convenience diff --git a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml index 8e6f0150..ff34486d 100644 --- a/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml @@ -351,7 +351,8 @@ IterGpuInfo(bool forceRefresh = false) return gpuInfos; } + /// + /// Gets the NVIDIA driver version using nvidia-smi. + /// Returns null if nvidia-smi is not available or fails. + /// + public static Version? GetNvidiaDriverVersion() + { + try + { + var psi = new ProcessStartInfo + { + FileName = "nvidia-smi", + UseShellExecute = false, + Arguments = "--query-gpu=driver_version --format=csv,noheader", + RedirectStandardOutput = true, + CreateNoWindow = true, + }; + + var process = Process.Start(psi); + process?.WaitForExit(); + var stdout = process?.StandardOutput.ReadToEnd()?.Trim(); + + if (string.IsNullOrEmpty(stdout)) + return null; + + // Driver version is typically in the format "xxx.xx" (e.g., "591.59") + // We'll parse it as a Version object + return Version.TryParse(stdout, out var version) ? version : null; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to get NVIDIA driver version from nvidia-smi"); + return null; + } + } + /// /// Return true if the system has at least one Nvidia GPU. /// diff --git a/StabilityMatrix.Core/Models/Packages/AiToolkit.cs b/StabilityMatrix.Core/Models/Packages/AiToolkit.cs index 24009cef..fc1e388e 100644 --- a/StabilityMatrix.Core/Models/Packages/AiToolkit.cs +++ b/StabilityMatrix.Core/Models/Packages/AiToolkit.cs @@ -35,10 +35,7 @@ IPyInstallationManager pyInstallationManager public override string LicenseUrl => "https://github.com/ostris/ai-toolkit/blob/main/LICENSE"; public override string LaunchCommand => string.Empty; - public override Uri PreviewImageUri => - new( - "https://camo.githubusercontent.com/ea35b399e0d659f9f2ee09cbedb58e1a3ec7a0eab763e8ae8d11d076aad5be40/68747470733a2f2f6f73747269732e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032352f30322f746f6f6c6b69742d75692e6a7067" - ); + public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/aitoolkit/preview.webp"); public override string OutputFolderName => "output"; public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda]; diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 6af2b047..afd8a8b3 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -491,6 +491,59 @@ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage VenvRunner.UpdateEnvironmentVariables(GetEnvVars); + // Check for old NVIDIA driver version with cu130 installations + var isNvidia = SettingsManager.Settings.PreferredGpu?.IsNvidia ?? HardwareHelper.HasNvidiaGpu(); + var isLegacyNvidia = + SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu(); + + if (isNvidia && !isLegacyNvidia) + { + var driverVersion = HardwareHelper.GetNvidiaDriverVersion(); + if (driverVersion is not null && driverVersion.Major < 580) + { + // Check if torch is installed with cu130 index + var torchInfo = await VenvRunner.PipShow("torch").ConfigureAwait(false); + if (torchInfo is not null) + { + var version = torchInfo.Version; + var plusPos = version.IndexOf('+'); + var torchIndex = plusPos >= 0 ? version[(plusPos + 1)..] : string.Empty; + + // Only warn if using cu130 (which requires driver 580+) + if (torchIndex.Equals("cu130", StringComparison.OrdinalIgnoreCase)) + { + var warningMessage = $""" + + ============================================================ + NVIDIA DRIVER WARNING + ============================================================ + + Your NVIDIA driver version ({driverVersion}) is older than + the minimum required version (580.x) for CUDA 13.0 (cu130). + + This may cause ComfyUI to fail to start or experience issues. + + Recommended actions: + 1. Update your NVIDIA driver to version 580 or newer + 2. Or manually downgrade your torch version to use an + older torch index (e.g. cu128) + + ============================================================ + + """; + + Logger.Warn( + "NVIDIA driver version {DriverVersion} is below 580.x minimum for cu130 (torch index: {TorchIndex})", + driverVersion, + torchIndex + ); + onConsoleOutput?.Invoke(ProcessOutput.FromStdErrLine(warningMessage)); + return; + } + } + } + } + VenvRunner.RunDetached( [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments], HandleConsoleOutput, diff --git a/StabilityMatrix.Core/Models/Packages/FluxGym.cs b/StabilityMatrix.Core/Models/Packages/FluxGym.cs index dfb9d074..5c356f76 100644 --- a/StabilityMatrix.Core/Models/Packages/FluxGym.cs +++ b/StabilityMatrix.Core/Models/Packages/FluxGym.cs @@ -30,8 +30,7 @@ IPyInstallationManager pyInstallationManager public override string LicenseUrl => ""; public override string LaunchCommand => "app.py"; - public override Uri PreviewImageUri => - new("https://raw.githubusercontent.com/cocktailpeanut/fluxgym/main/screenshot.png"); + public override Uri PreviewImageUri => new("https://cdn.lykos.ai/sm/packages/fluxgym/fluxgym.webp"); public override List LaunchOptions => [LaunchOptionDefinition.Extras]; diff --git a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs index ed7ac484..3f7cb24f 100644 --- a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs +++ b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs @@ -122,6 +122,11 @@ public override Task DownloadPackage( return Task.CompletedTask; } + public override Task CheckForUpdates(InstalledPackage package) => + package.PythonVersion == Python.PyInstallationManager.Python_3_10_11.ToString() + ? Task.FromResult(false) + : base.CheckForUpdates(package); + public override async Task InstallPackage( string installLocation, InstalledPackage installedPackage, @@ -291,6 +296,44 @@ await SetupVenv(installedPackagePath, pythonVersion: PyVersion.Parse(installedPa VenvRunner.UpdateEnvironmentVariables(env => GetEnvVars(env, installedPackagePath)); + // Check for legacy Python 3.10.11 installations + if (installedPackage.PythonVersion == Python.PyInstallationManager.Python_3_10_11.ToString()) + { + var warningMessage = """ + + ============================================================ + LEGACY INVOKEAI INSTALLATION + ============================================================ + + This InvokeAI installation is using Python 3.10.11, which + is no longer supported by InvokeAI. + + Automatic updates have been disabled for this installation + to prevent compatibility issues. + + Your current installation will continue to work, but will + not receive updates. + + Recommended actions: + 1. Install a new InvokeAI instance with Python 3.12+ + 2. Copy your settings and data from this installation + to the new one + 3. Once verified, you can delete this old installation + + Note: You can run both installations side-by-side during + the migration. + + ============================================================ + + """; + + Logger.Warn( + "InvokeAI installation using legacy Python {PythonVersion} - updates disabled", + installedPackage.PythonVersion + ); + onConsoleOutput?.Invoke(ProcessOutput.FromStdErrLine(warningMessage)); + } + // Launch command is for a console entry point, and not a direct script var entryPoint = await VenvRunner.GetEntryPoint(command).ConfigureAwait(false); From 873bfc5f5560d7f66fa19a5d9eb3fc3b0110ef94 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jan 2026 01:29:45 -0800 Subject: [PATCH 02/14] fix chagenlog --- CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26f29d90..29fa4e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,7 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). -<<<<<<< HEAD -## v2.15.5 -======= -## v2.16.0-dev.2 +## v2.15.6 ### Added - Added NVIDIA driver version warning when launching ComfyUI with CUDA 13.0 (cu130) and driver versions below 580.x - Added legacy Python warning when launching InvokeAI installations using Python 3.10.11 @@ -17,8 +14,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ### Fixed - Hide rating stars in the Civitai browser page if no rating is available -## v2.16.0-dev.1 ->>>>>>> 0c792e7c (Merge pull request #1189 from ionite34/old-stuff-notifications) +## v2.15.5 ### Added - Added new package - [Wan2GP](https://github.com/deepbeepmeep/Wan2GP) - Added [Stable Diffusion WebUI Forge - Neo](https://github.com/Haoming02/sd-webui-forge-classic/tree/neo) as a separate package for convenience From e9149df98eefedaa0b46d5396aaea0fef0458037 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 25 Jan 2026 19:14:53 -0800 Subject: [PATCH 03/14] Merge pull request #1193 from ionite34/fix-bugs-n-stuff Fix bugs n stuff (cherry picked from commit 1c542602ee2ade9af1e0835e02e2b0e5579068cc) # Conflicts: # CHANGELOG.md # StabilityMatrix.Core/Models/Packages/Wan2GP.cs --- CHANGELOG.md | 57 ++ StabilityMatrix.Avalonia/App.axaml.cs | 25 + .../DesignData/MockLaunchPageViewModel.cs | 4 +- .../Helpers/UnixPrerequisiteHelper.cs | 25 +- .../Helpers/WindowsPrerequisiteHelper.cs | 55 +- .../Services/InferenceClientManager.cs | 23 +- .../CheckpointFileViewModel.cs | 44 +- .../ViewModels/CheckpointsPageViewModel.cs | 15 +- .../ViewModels/Dialogs/CivitFileViewModel.cs | 11 +- .../Dialogs/ModelVersionViewModel.cs | 6 +- .../Dialogs/OneClickInstallViewModel.cs | 12 +- .../ViewModels/MainWindowViewModel.cs | 6 +- .../PackageManager/PackageCardViewModel.cs | 14 +- .../PackageInstallDetailViewModel.cs | 45 +- .../Helper/Factory/PackageFactory.cs | 92 ++- .../Models/Database/LocalModelFile.cs | 6 - .../PackageModification/ActionPackageStep.cs | 20 + .../Models/Packages/A3WebUI.cs | 34 +- .../Models/Packages/AiToolkit.cs | 13 +- .../Models/Packages/BaseGitPackage.cs | 36 +- .../Models/Packages/Cogstudio.cs | 15 +- .../Models/Packages/ComfyUI.cs | 124 +++- .../Models/Packages/ComfyZluda.cs | 17 +- .../Models/Packages/DankDiffusion.cs | 12 +- .../Models/Packages/FluxGym.cs | 13 +- .../Models/Packages/FocusControlNet.cs | 13 +- .../Models/Packages/Fooocus.cs | 14 +- .../Models/Packages/FooocusMre.cs | 13 +- .../Models/Packages/ForgeAmdGpu.cs | 15 +- .../Models/Packages/ForgeClassic.cs | 190 ++++-- .../Models/Packages/ForgeNeo.cs | 16 +- .../Models/Packages/FramePack.cs | 13 +- .../Models/Packages/FramePackStudio.cs | 13 +- .../Models/Packages/InvokeAI.cs | 13 +- .../Models/Packages/KohyaSs.cs | 13 +- .../Models/Packages/Mashb1tFooocus.cs | 13 +- .../Models/Packages/OneTrainer.cs | 13 +- .../Models/Packages/Reforge.cs | 13 +- .../Models/Packages/RuinedFooocus.cs | 13 +- .../Models/Packages/SDWebForge.cs | 15 +- StabilityMatrix.Core/Models/Packages/Sdfx.cs | 13 +- .../Models/Packages/SimpleSDXL.cs | 45 +- .../Packages/StableDiffusionDirectMl.cs | 15 +- .../Models/Packages/StableDiffusionUx.cs | 13 +- .../Models/Packages/StableSwarm.cs | 13 +- .../Models/Packages/VladAutomatic.cs | 15 +- .../Models/Packages/VoltaML.cs | 13 +- .../Models/Packages/Wan2GP.cs | 91 ++- StabilityMatrix.Core/Python/UvVenvRunner.cs | 8 + .../Services/IPipWheelService.cs | 55 ++ .../Services/ModelIndexService.cs | 81 --- .../Services/PipWheelService.cs | 588 ++++++++++++++++++ .../Helper/PackageFactoryTests.cs | 16 +- .../Models/Packages/PackageHelper.cs | 3 +- 54 files changed, 1660 insertions(+), 413 deletions(-) create mode 100644 StabilityMatrix.Core/Models/PackageModification/ActionPackageStep.cs create mode 100644 StabilityMatrix.Core/Services/IPipWheelService.cs create mode 100644 StabilityMatrix.Core/Services/PipWheelService.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b96203..a05271ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,63 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +<<<<<<< HEAD +======= +## v2.16.0-dev.2 +### Added +- Added NVIDIA driver version warning when launching ComfyUI with CUDA 13.0 (cu130) and driver versions below 580.x +- Added legacy Python warning when launching InvokeAI installations using Python 3.10.11 +- Added Tiled VAE Decode to the Inference video workflows - thanks to @NeuralFault! +### Changed +- Disabled update checking for legacy InvokeAI installations using Python 3.10.11 +- Hide rating stars in the Civitai browser page if no rating is available +- Updated uv to v0.9.26 +- Updated PortableGit to v2.52.0.windows.1 +- Updated Sage/Triton/Nunchaku installers to use GitHub API to fetch latest releases +- Updated ComfyUI installations and updates to automatically install ComfyUI Manager +- Updated gfx110X Windows ROCm nightly index - thanks to @NeuralFault! +- Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault! +- Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku +### Fixed +- Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub +- Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model +- Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo +- Fixed [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things +- Fixed [#1505](https://github.com/LykosAI/StabilityMatrix/issues/1505) - incorrect port argument for Wan2GP +- Possibly fix [#1502](https://github.com/LykosAI/StabilityMatrix/issues/1502) - English fonts not displaying correctly on Linux in Chinese environments +- Fixed [#1476](https://github.com/LykosAI/StabilityMatrix/issues/1476) - Incorrect shared output folder for Forge Classic/Neo +- Fixed [#1466](https://github.com/LykosAI/StabilityMatrix/issues/1466) - crash after moving portable install +- Fixed [#1445](https://github.com/LykosAI/StabilityMatrix/issues/1445) - Linux app updates not actually updating - thanks to @NeuralFault! + +## v2.16.0-dev.1 +### Added +#### New Feature: 🧪 Image Lab - Conversational Image Generation for ComfyUI +- We've added a brand new conversational interface for image generation! Image Lab lets you iterate on images naturally through chat, rather than just one-off prompts. + - Local-First Power: Native support for Flux Kontext and Qwen Image Edit running entirely locally via your ComfyUI backend. + - Smart Setup: Stability Matrix automatically detects and helps you download the specific models and LoRAs needed for these local workflows. + - Interactive Tools: Drag-and-drop image inputs, use the built-in annotation tool to draw on images, and keep persistent conversation history. + - Cloud Option: Includes optional support for Nano Banana (Gemini 3 Pro/2.5) for users who want to leverage external reasoning models. +- Added new package - [Wan2GP](https://github.com/deepbeepmeep/Wan2GP) +- Added [Stable Diffusion WebUI Forge - Neo](https://github.com/Haoming02/sd-webui-forge-classic/tree/neo) as a separate package for convenience +- Added Intel GPU support for ComfyUI +- Added "Run Python Command" option to the package card's 3-dots menu for running arbitrary Python code in the package's virtual environment +- Added togglable `--uv` argument to the SD.Next launch options +- Added Tiled VAE decoding as an Inference addon thanks to @NeuralFault! +### Changed +- Moved the original Stable Diffusion WebUI Forge to the "Legacy" packages tab due to inactivity +- Updated to cu130 torch index for ComfyUI installs with Nvidia GPUs +- Consolidated and fixed AMD GPU architecture detection +- Updated SageAttention installer to latest v2.2.0-windows.post4 version +- Video files can now be opened directly from the Output browser +- Videos will now appear with thumbnails in the Output browser +### Fixed +- Fixed [#1450](https://github.com/LykosAI/StabilityMatrix/issues/1450) - Older SD.Next not launching due to forced `--uv` argument +- Fixed duplicate custom node installations when installing workflows from the Workflow Browser - thanks again to @NeuralFault! +### Supporters +#### 🌟 Visionaries +A massive thank you to our esteemed Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your generosity is the powerhouse behind Stability Matrix, enabling us to keep building and refining with confidence. We are truly grateful for your partnership! + +>>>>>>> 1c542602 (Merge pull request #1193 from ionite34/fix-bugs-n-stuff) ## v2.15.5 ### Added - Added new package - [Wan2GP](https://github.com/deepbeepmeep/Wan2GP) diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index e3144550..25e8db13 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -250,6 +250,28 @@ public FontFamily GetPlatformDefaultFontFamily() fonts.Add("Helvetica Neue"); fonts.Add("Helvetica"); } + else if (Compat.IsLinux) + { + // For Chinese locales, prioritize CJK-capable fonts first + if (Cultures.Current?.Name is "zh-Hans" or "zh-Hant") + { + // Common Chinese fonts on Linux systems + fonts.Add("Noto Sans CJK SC"); + fonts.Add("Noto Sans CJK TC"); + fonts.Add("Source Han Sans"); + fonts.Add("WenQuanYi Micro Hei"); + fonts.Add("WenQuanYi Zen Hei"); + } + + // Add common Linux fonts + fonts.Add("Ubuntu"); + fonts.Add("DejaVu Sans"); + + // Fallback to system default + fonts.Add(FontFamily.Default.Name); + + return new FontFamily(string.Join(",", fonts)); + } else { return FontFamily.Default; @@ -281,6 +303,9 @@ private void Setup() Logger.Debug("ActivatableLifetime available, setting up activation protocol handlers"); activatableLifetime.Activated += OnActivated; } + + // Update font when culture/language changes + EventManager.Instance.CultureChanged += (_, _) => SetFontFamily(GetPlatformDefaultFontFamily()); } private void ShowMainWindow() diff --git a/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs b/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs index b6b406c9..a10f4400 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs @@ -36,13 +36,13 @@ IServiceManager dialogFactory public override BasePackage? SelectedBasePackage => SelectedPackage?.PackageName != "dank-diffusion" ? base.SelectedBasePackage - : new DankDiffusion(null!, null!, null!, null!, null!); + : new DankDiffusion(null!, null!, null!, null!, null!, null!); protected override Task LaunchImpl(string? command) { IsLaunchTeachingTipsOpen = false; - RunningPackage = new PackagePair(null!, new DankDiffusion(null!, null!, null!, null!, null!)); + RunningPackage = new PackagePair(null!, new DankDiffusion(null!, null!, null!, null!, null!, null!)); Console.Document.Insert( 0, diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index 12fb063e..009d1f56 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -35,9 +35,9 @@ IPyInstallationManager pyInstallationManager private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private const string UvMacDownloadUrl = - "https://github.com/astral-sh/uv/releases/download/0.8.4/uv-aarch64-apple-darwin.tar.gz"; + "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz"; private const string UvLinuxDownloadUrl = - "https://github.com/astral-sh/uv/releases/download/0.8.4/uv-x86_64-unknown-linux-gnu.tar.gz"; + "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz"; private DirectoryPath HomeDir => settingsManager.LibraryDir; private DirectoryPath AssetsDir => HomeDir.JoinDir("Assets"); @@ -75,7 +75,7 @@ private bool IsPythonVersionInstalled(PyVersion version) => // Cached store of whether or not git is installed private bool? isGitInstalled; - private string ExpectedUvVersion => "0.8.4"; + private string ExpectedUvVersion => "0.9.26"; public bool IsVcBuildToolsInstalled => false; public bool IsHipSdkInstalled => false; @@ -272,7 +272,13 @@ private async Task RunGit(ProcessArgs args, string? workingDirectory = null) var result = await ProcessRunner.RunBashCommand( command, workingDirectory ?? string.Empty, - new Dictionary { { "GIT_TERMINAL_PROMPT", "0" } } + new Dictionary + { + { "GIT_TERMINAL_PROMPT", "0" }, + // Set UTF-8 locale to handle Unicode characters in paths + { "LC_ALL", "C.UTF-8" }, + { "LANG", "C.UTF-8" }, + } ); if (result.ExitCode != 0) { @@ -375,7 +381,16 @@ public async Task InstallPythonIfNecessary(PyVersion version, IProgress GetGitOutput(ProcessArgs args, string? workingDirectory = null) { - return ProcessRunner.RunBashCommand(args.Prepend("git"), workingDirectory ?? ""); + return ProcessRunner.RunBashCommand( + args.Prepend("git"), + workingDirectory ?? "", + new Dictionary + { + // Set UTF-8 locale to handle Unicode characters in paths + { "LC_ALL", "C.UTF-8" }, + { "LANG", "C.UTF-8" }, + } + ); } private async Task RunNode( diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index 18ee8741..2a5ed558 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -27,7 +27,8 @@ IPyInstallationManager pyInstallationManager private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private const string PortableGitDownloadUrl = - "https://github.com/git-for-windows/git/releases/download/v2.41.0.windows.1/PortableGit-2.41.0-64-bit.7z.exe"; + "https://github.com/git-for-windows/git/releases/download/v2.52.0.windows.1/PortableGit-2.52.0-64-bit.7z.exe"; + private const string ExpectedGitVersion = "2.52.0"; private const string VcRedistDownloadUrl = "https://aka.ms/vs/16/release/vc_redist.x64.exe"; @@ -49,7 +50,7 @@ IPyInstallationManager pyInstallationManager private const string PythonLibsDownloadUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip"; private const string UvWindowsDownloadUrl = - "https://github.com/astral-sh/uv/releases/download/0.8.4/uv-x86_64-pc-windows-msvc.zip"; + "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip"; private string HomeDir => settingsManager.LibraryDir; @@ -115,7 +116,7 @@ private string GetPythonLibraryZipPath(PyVersion version) => private string UvExtractPath => Path.Combine(AssetsDir, "uv"); public string UvExePath => Path.Combine(UvExtractPath, "uv.exe"); public bool IsUvInstalled => File.Exists(UvExePath); - private string ExpectedUvVersion => "0.8.4"; + private string ExpectedUvVersion => "0.9.26"; public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin"); public bool IsVcBuildToolsInstalled => Directory.Exists(VcBuildToolsExistsPath); @@ -155,6 +156,10 @@ public async Task RunGit( { { "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) }, { "GIT_TERMINAL_PROMPT", "0" }, + // Set UTF-8 locale to handle Unicode characters in paths + // This helps Git load libcurl-4.dll when paths contain non-ASCII characters + { "LC_ALL", "C.UTF-8" }, + { "LANG", "C.UTF-8" }, } ); await process.WaitForExitAsync().ConfigureAwait(false); @@ -173,6 +178,10 @@ public Task GetGitOutput(ProcessArgs args, string? workingDirecto environmentVariables: new Dictionary { { "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) }, + // Set UTF-8 locale to handle Unicode characters in paths + // This helps Git load libcurl-4.dll when paths contain non-ASCII characters + { "LC_ALL", "C.UTF-8" }, + { "LANG", "C.UTF-8" }, } ); } @@ -605,11 +614,31 @@ public async Task InstallGitIfNecessary(IProgress? progress = nu { if (File.Exists(GitExePath)) { - Logger.Debug("Git already installed at {GitExePath}", GitExePath); - return; + var installedVersion = await GetInstalledGitVersionAsync(); + if (installedVersion.Contains(ExpectedGitVersion)) + { + Logger.Debug( + "Git {Version} already installed at {GitExePath}", + ExpectedGitVersion, + GitExePath + ); + return; + } + + Logger.Info( + "Git version mismatch. Installed: {Installed}, Expected: {Expected}. Upgrading...", + installedVersion.Trim(), + ExpectedGitVersion + ); + + // Delete existing installation to upgrade + if (Directory.Exists(PortableGitInstallDir)) + { + await new DirectoryPath(PortableGitInstallDir).DeleteAsync(true); + } } - Logger.Info("Git not found at {GitExePath}, downloading...", GitExePath); + Logger.Info("Git not found or outdated at {GitExePath}, downloading...", GitExePath); // Download if (!File.Exists(PortableGitDownloadPath)) @@ -1139,6 +1168,20 @@ private async Task GetInstalledUvVersionAsync() } } + private async Task GetInstalledGitVersionAsync() + { + try + { + var result = await GetGitOutput(["--version"]); + return result.StandardOutput ?? string.Empty; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to get Git version"); + return string.Empty; + } + } + private async Task EnsurePythonVersion(PyVersion pyVersion) { var result = await pyInstallationManager.GetInstallationAsync(pyVersion); diff --git a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs index 4804b7b3..3862cf21 100644 --- a/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs +++ b/StabilityMatrix.Avalonia/Services/InferenceClientManager.cs @@ -331,15 +331,22 @@ ICompletionProvider completionProvider if (!settingsManager.IsLibraryDirSet) return; - ResetSharedProperties(); - - if (IsConnected) + // Dispatch to UI thread to prevent race conditions with Avalonia's selection model. + // The ModelIndexChanged event may be raised from a background thread, and modifying + // observable collections from a non-UI thread can cause ArgumentOutOfRangeException + // when the selection model tries to enumerate selected items. + Dispatcher.UIThread.Post(() => { - LoadSharedPropertiesAsync() - .SafeFireAndForget(onException: ex => - logger.LogError(ex, "Error loading shared properties") - ); - } + ResetSharedProperties(); + + if (IsConnected) + { + LoadSharedPropertiesAsync() + .SafeFireAndForget(onException: ex => + logger.LogError(ex, "Error loading shared properties") + ); + } + }); }; } diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs index 0f06ef28..eb8be6ab 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs @@ -321,41 +321,35 @@ private async Task RenameAsync() [RelayCommand] private async Task OpenSafetensorMetadataViewer() { - if (!CheckpointFile.SafetensorMetadataParsed) + if ( + !settingsManager.IsLibraryDirSet + || new DirectoryPath(settingsManager.ModelsDirectory) is not { Exists: true } modelsDir + ) { - if ( - !settingsManager.IsLibraryDirSet - || new DirectoryPath(settingsManager.ModelsDirectory) is not { Exists: true } modelsDir - ) - { - return; - } - - try - { - var safetensorPath = CheckpointFile.GetFullPath(modelsDir); - - var metadata = await SafetensorMetadata.ParseAsync(safetensorPath); - - CheckpointFile.SafetensorMetadataParsed = true; - CheckpointFile.SafetensorMetadata = metadata; - } - catch (Exception ex) - { - logger.LogWarning(ex, "Failed to parse safetensor metadata"); - return; - } + return; } - if (!CheckpointFile.SafetensorMetadataParsed) + SafetensorMetadata? metadata; + try + { + var safetensorPath = CheckpointFile.GetFullPath(modelsDir); + metadata = await SafetensorMetadata.ParseAsync(safetensorPath); + } + catch (Exception ex) { + logger.LogWarning(ex, "Failed to parse safetensor metadata"); + notificationService.Show( + "No Metadata Found", + "This safetensor file does not contain any embedded metadata.", + NotificationType.Warning + ); return; } var vm = vmFactory.Get(vm => { vm.ModelName = CheckpointFile.DisplayModelName; - vm.Metadata = CheckpointFile.SafetensorMetadata; + vm.Metadata = metadata; }); var dialog = vm.GetDialog(); diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs index d99a1fb4..62bea1c0 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs @@ -403,11 +403,16 @@ or nameof(SortConnectedModelsFirst) EventManager.Instance.ModelIndexChanged += (_, _) => { - RefreshCategories(); - ModelsCache.EditDiff( - modelIndexService.ModelIndex.Values.SelectMany(x => x), - LocalModelFile.RelativePathConnectedModelInfoComparer - ); + // Dispatch to UI thread to prevent race conditions with Avalonia's selection model. + // The ModelIndexChanged event may be raised from a background thread. + Dispatcher.UIThread.Post(() => + { + RefreshCategories(); + ModelsCache.EditDiff( + modelIndexService.ModelIndex.Values.SelectMany(x => x), + LocalModelFile.RelativePathConnectedModelInfoComparer + ); + }); }; AddDisposable( diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitFileViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitFileViewModel.cs index 34a9bd0f..b3423d51 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitFileViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitFileViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Threading.Tasks; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; @@ -90,9 +91,13 @@ public CivitFileViewModel( private void ModelIndexChanged(object? sender, EventArgs e) { - IsInstalled = - CivitFile is { Type: CivitFileType.Model, Hashes.BLAKE3: not null } - && modelIndexService.ModelIndexBlake3Hashes.Contains(CivitFile.Hashes.BLAKE3); + // Dispatch to UI thread since the event may be raised from a background thread + Dispatcher.UIThread.Post(() => + { + IsInstalled = + CivitFile is { Type: CivitFileType.Model, Hashes.BLAKE3: not null } + && modelIndexService.ModelIndexBlake3Hashes.Contains(CivitFile.Hashes.BLAKE3); + }); } [RelayCommand(CanExecute = nameof(CanExecuteDownload))] diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs index 82e428b3..9e5a9712 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs @@ -1,4 +1,5 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.Api; @@ -42,7 +43,8 @@ public void RefreshInstallStatus() private void ModelIndexChanged(object? sender, EventArgs e) { - RefreshInstallStatus(); + // Dispatch to UI thread since the event may be raised from a background thread + Dispatcher.UIThread.Post(RefreshInstallStatus); } protected override void Dispose(bool disposing) diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs index 3a452c1a..c1d790c1 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs @@ -88,7 +88,8 @@ INavigationService navigationService SubHeaderText = Resources.Text_OneClickInstaller_SubHeader; ShowInstallButton = true; - var filteredPackages = this.packageFactory.GetAllAvailablePackages() + var filteredPackages = this + .packageFactory.GetAllAvailablePackages() .Where(p => p is { OfferInOneClickInstaller: true, IsCompatible: true }) .ToList(); @@ -134,7 +135,7 @@ private async Task DoInstall() prerequisiteHelper, SelectedPackage, PyInstallationManager.Python_3_10_17 - ) + ), }; // get latest version & download & install @@ -160,6 +161,7 @@ private async Task DoInstall() var torchVersion = SelectedPackage.GetRecommendedTorchVersion(); var recommendedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod; + var recommendedPython = SelectedPackage.RecommendedPythonVersion; var downloadStep = new DownloadPackageVersionStep( SelectedPackage, @@ -179,7 +181,7 @@ private async Task DoInstall() LastUpdateCheck = DateTimeOffset.Now, PreferredTorchIndex = torchVersion, PreferredSharedFolderMethod = recommendedSharedFolderMethod, - PythonVersion = PyInstallationManager.Python_3_10_17.StringValue + PythonVersion = recommendedPython.StringValue, }; var installStep = new InstallPackageStep( @@ -190,7 +192,7 @@ private async Task DoInstall() { SharedFolderMethod = recommendedSharedFolderMethod, VersionOptions = downloadVersion, - PythonOptions = { TorchIndex = torchVersion } + PythonOptions = { TorchIndex = torchVersion, PythonVersion = recommendedPython }, } ); steps.Add(installStep); @@ -209,7 +211,7 @@ private async Task DoInstall() { ShowDialogOnStart = true, HideCloseButton = true, - ModificationCompleteMessage = $"{SelectedPackage.DisplayName} installed successfully" + ModificationCompleteMessage = $"{SelectedPackage.DisplayName} installed successfully", }; EventManager.Instance.OnPackageInstallProgressAdded(runner); await runner.ExecuteSteps(steps); diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index 48d4a435..597e0b8d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -179,6 +179,10 @@ protected override async Task OnInitialLoadedAsync() // Initialize Discord Rich Presence (this needs LibraryDir so is set here) discordRichPresenceService.UpdateState(); + // Ensure GPU compute capability is populated before other components check it + // This must run early and be awaited to prevent race conditions with package initialization + await Task.Run(AddComputeCapabilityIfNecessary); + // Load in-progress downloads ProgressManagerViewModel.AddDownloads(trackedDownloadService.Downloads); @@ -275,8 +279,6 @@ settingsManager.Settings.Analytics.LastSeenConsentVersion is null .SafeFireAndForget(); } - Task.Run(AddComputeCapabilityIfNecessary).SafeFireAndForget(); - // Show what's new for updates if (settingsManager.Settings.UpdatingFromVersion is { } updatingFromVersion) { diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index a8ffb315..1791b786 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -483,7 +483,11 @@ public async Task Update() new UpdatePackageOptions { VersionOptions = versionOptions, - PythonOptions = { TorchIndex = Package.PreferredTorchIndex }, + PythonOptions = + { + TorchIndex = Package.PreferredTorchIndex, + PythonVersion = PyVersion.TryParse(Package.PythonVersion, out var pv) ? pv : null, + }, } ); var steps = new List { updatePackageStep }; @@ -652,7 +656,13 @@ private async Task ChangeVersion() new UpdatePackageOptions { VersionOptions = versionOptions, - PythonOptions = { TorchIndex = Package.PreferredTorchIndex }, + PythonOptions = + { + TorchIndex = Package.PreferredTorchIndex, + PythonVersion = PyVersion.TryParse(Package.PythonVersion, out var pyVer) + ? pyVer + : null, + }, } ); var steps = new List { updatePackageStep }; diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs index 299a849d..e69614be 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs @@ -237,25 +237,40 @@ x.PackageName is nameof(ComfyUI) or "ComfyUI-Zluda" var installedVersion = new InstalledPackageVersion(); if (IsReleaseMode) { - downloadOptions.VersionTag = - SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null"); - downloadOptions.IsLatest = AvailableVersions?.First().TagName == downloadOptions.VersionTag; - downloadOptions.IsPrerelease = SelectedVersion.IsPrerelease; + if (SelectedVersion is not null) + { + downloadOptions.VersionTag = SelectedVersion.TagName; + downloadOptions.IsLatest = + AvailableVersions?.FirstOrDefault()?.TagName == downloadOptions.VersionTag; + downloadOptions.IsPrerelease = SelectedVersion.IsPrerelease; - installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag; - installedVersion.IsPrerelease = SelectedVersion.IsPrerelease; + installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag; + installedVersion.IsPrerelease = SelectedVersion.IsPrerelease; + } + else + { + downloadOptions.IsLatest = true; + downloadOptions.BranchName = SelectedPackage.MainBranch; + installedVersion.InstalledBranch = SelectedPackage.MainBranch; + } } else { - downloadOptions.CommitHash = - SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null"); - downloadOptions.BranchName = - SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null"); - downloadOptions.IsLatest = AvailableCommits?.First().Sha == SelectedCommit.Sha; - - installedVersion.InstalledBranch = - SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null"); - installedVersion.InstalledCommitSha = downloadOptions.CommitHash; + if (SelectedCommit is not null && SelectedVersion is not null) + { + downloadOptions.CommitHash = SelectedCommit.Sha; + downloadOptions.BranchName = SelectedVersion.TagName; + downloadOptions.IsLatest = AvailableCommits?.FirstOrDefault()?.Sha == SelectedCommit.Sha; + + installedVersion.InstalledBranch = SelectedVersion.TagName; + installedVersion.InstalledCommitSha = downloadOptions.CommitHash; + } + else + { + downloadOptions.IsLatest = true; + downloadOptions.BranchName = SelectedPackage.MainBranch; + installedVersion.InstalledBranch = SelectedPackage.MainBranch; + } } var pipOverrides = PythonPackageSpecifiersViewModel.GetSpecifiers().ToList(); diff --git a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs index 9d759e3f..118efa55 100644 --- a/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs +++ b/StabilityMatrix.Core/Helper/Factory/PackageFactory.cs @@ -17,6 +17,7 @@ public class PackageFactory : IPackageFactory private readonly IPyRunner pyRunner; private readonly IUvManager uvManager; private readonly IPyInstallationManager pyInstallationManager; + private readonly IPipWheelService pipWheelService; /// /// Mapping of package.Name to package @@ -30,7 +31,8 @@ public PackageFactory( IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, IPyInstallationManager pyInstallationManager, - IPyRunner pyRunner + IPyRunner pyRunner, + IPipWheelService pipWheelService ) { this.githubApiCache = githubApiCache; @@ -39,6 +41,7 @@ IPyRunner pyRunner this.prerequisiteHelper = prerequisiteHelper; this.pyRunner = pyRunner; this.pyInstallationManager = pyInstallationManager; + this.pipWheelService = pipWheelService; this.basePackages = basePackages.ToDictionary(x => x.Name); } @@ -51,42 +54,48 @@ public BasePackage GetNewBasePackage(InstalledPackage installedPackage) settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "Fooocus" => new Fooocus( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "stable-diffusion-webui" => new A3WebUI( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "Fooocus-ControlNet-SDXL" => new FocusControlNet( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "Fooocus-MRE" => new FooocusMre( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "InvokeAI" => new InvokeAI( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "kohya_ss" => new KohyaSs( githubApiCache, @@ -94,161 +103,184 @@ public BasePackage GetNewBasePackage(InstalledPackage installedPackage) downloadService, prerequisiteHelper, pyRunner, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "OneTrainer" => new OneTrainer( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "RuinedFooocus" => new RuinedFooocus( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "stable-diffusion-webui-forge" => new SDWebForge( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "stable-diffusion-webui-directml" => new StableDiffusionDirectMl( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "stable-diffusion-webui-ux" => new StableDiffusionUx( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "StableSwarmUI" => new StableSwarm( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "automatic" => new VladAutomatic( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "voltaML-fast-stable-diffusion" => new VoltaML( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "sdfx" => new Sdfx( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "mashb1t-fooocus" => new Mashb1tFooocus( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "reforge" => new Reforge( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "FluxGym" => new FluxGym( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "SimpleSDXL" => new SimpleSDXL( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "Cogstudio" => new Cogstudio( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "ComfyUI-Zluda" => new ComfyZluda( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "stable-diffusion-webui-amdgpu-forge" => new ForgeAmdGpu( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "forge-classic" => new ForgeClassic( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "forge-neo" => new ForgeNeo( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "framepack" => new FramePack( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "framepack-studio" => new FramePackStudio( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "ai-toolkit" => new AiToolkit( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), "Wan2GP" => new Wan2GP( githubApiCache, settingsManager, downloadService, prerequisiteHelper, - pyInstallationManager + pyInstallationManager, + pipWheelService ), _ => throw new ArgumentOutOfRangeException(nameof(installedPackage)), }; diff --git a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs index 0dcbe928..0e0581b9 100644 --- a/StabilityMatrix.Core/Models/Database/LocalModelFile.cs +++ b/StabilityMatrix.Core/Models/Database/LocalModelFile.cs @@ -164,12 +164,6 @@ public override int GetHashCode() public bool HasOpenModelDbMetadata => HasConnectedModel && ConnectedModelInfo.Source == ConnectedModelSource.OpenModelDb; - [BsonIgnore] - public SafetensorMetadata? SafetensorMetadata { get; set; } - - [BsonIgnore] - public bool SafetensorMetadataParsed { get; set; } - public string GetFullPath(string rootModelDirectory) { return Path.Combine(rootModelDirectory, RelativePath); diff --git a/StabilityMatrix.Core/Models/PackageModification/ActionPackageStep.cs b/StabilityMatrix.Core/Models/PackageModification/ActionPackageStep.cs new file mode 100644 index 00000000..33a632f8 --- /dev/null +++ b/StabilityMatrix.Core/Models/PackageModification/ActionPackageStep.cs @@ -0,0 +1,20 @@ +using StabilityMatrix.Core.Models.Progress; + +namespace StabilityMatrix.Core.Models.PackageModification; + +/// +/// A package step that wraps an async action, useful for ad-hoc operations +/// that need to run within the PackageModificationRunner. +/// +public class ActionPackageStep( + Func, Task> action, + string progressTitle = "Working..." +) : IPackageStep +{ + public string ProgressTitle => progressTitle; + + public async Task ExecuteAsync(IProgress? progress) + { + await action(progress ?? new Progress()).ConfigureAwait(false); + } +} diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index 10d44e30..c35f00aa 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; @@ -23,8 +24,17 @@ public class A3WebUI( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -38,11 +48,9 @@ IPyInstallationManager pyInstallationManager public override string LaunchCommand => "launch.py"; public override Uri PreviewImageUri => new("https://github.com/AUTOMATIC1111/stable-diffusion-webui/raw/master/screenshot.png"); - public string RelativeArgsDefinitionScriptPath => "modules.cmd_args"; - public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple; - public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; + public override PackageType PackageType => PackageType.SdInference; // From https://github.com/AUTOMATIC1111/stable-diffusion-webui/tree/master/models public override Dictionary> SharedFolders => @@ -188,7 +196,7 @@ IPyInstallationManager pyInstallationManager public override IEnumerable AvailableTorchIndices => [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm, TorchIndex.Mps]; - public override string MainBranch => "master"; + public override string MainBranch => "dev"; public override string OutputFolderName => "outputs"; @@ -266,6 +274,8 @@ public override async Task RunPackage( await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)) .ConfigureAwait(false); + VenvRunner.UpdateEnvironmentVariables(GetEnvVars); + void HandleConsoleOutput(ProcessOutput s) { onConsoleOutput?.Invoke(s); @@ -296,6 +306,16 @@ void HandleConsoleOutput(ProcessOutput s) public override IReadOnlyList ExtraLaunchArguments => settingsManager.IsLibraryDirSet ? ["--gradio-allowed-path", settingsManager.ImagesDirectory] : []; + private ImmutableDictionary GetEnvVars(ImmutableDictionary env) + { + // Set the Stable Diffusion repository URL to a working fork + // This is required because the original Stability-AI/stablediffusion repo was removed + // See: https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/17212 + env = env.SetItem("STABLE_DIFFUSION_REPO", "https://github.com/w-e-w/stablediffusion.git"); + + return env; + } + private class A3WebUiExtensionManager(A3WebUI package) : GitPackageExtensionManager(package.PrerequisiteHelper) { diff --git a/StabilityMatrix.Core/Models/Packages/AiToolkit.cs b/StabilityMatrix.Core/Models/Packages/AiToolkit.cs index fc1e388e..b732dad7 100644 --- a/StabilityMatrix.Core/Models/Packages/AiToolkit.cs +++ b/StabilityMatrix.Core/Models/Packages/AiToolkit.cs @@ -21,8 +21,17 @@ public class AiToolkit( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private AnsiProcess? npmProcess; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index 267196ba..1976f4f5 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -29,6 +29,7 @@ public abstract class BaseGitPackage : BasePackage protected readonly IDownloadService DownloadService; protected readonly IPrerequisiteHelper PrerequisiteHelper; protected readonly IPyInstallationManager PyInstallationManager; + protected readonly IPipWheelService PipWheelService; public IPyVenvRunner? VenvRunner; public virtual string RepositoryName => Name; @@ -68,7 +69,8 @@ protected BaseGitPackage( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService ) : base(settingsManager) { @@ -76,6 +78,7 @@ IPyInstallationManager pyInstallationManager DownloadService = downloadService; PrerequisiteHelper = prerequisiteHelper; PyInstallationManager = pyInstallationManager; + PipWheelService = pipWheelService; } public override async Task GetLatestVersion( @@ -278,27 +281,30 @@ public override async Task DownloadPackage( ); } + var gitArgs = new List { "clone" }; + + var branchArg = !string.IsNullOrWhiteSpace(versionOptions.VersionTag) + ? versionOptions.VersionTag + : versionOptions.BranchName; + + if (!string.IsNullOrWhiteSpace(branchArg)) + { + gitArgs.Add("--branch"); + gitArgs.Add(branchArg); + } + + gitArgs.Add(GithubUrl); + gitArgs.Add(installLocation); + await PrerequisiteHelper - .RunGit( - new[] - { - "clone", - "--branch", - !string.IsNullOrWhiteSpace(versionOptions.VersionTag) - ? versionOptions.VersionTag - : versionOptions.BranchName ?? MainBranch, - GithubUrl, - installLocation, - }, - progress?.AsProcessOutputHandler() - ) + .RunGit(gitArgs.ToArray(), progress?.AsProcessOutputHandler()) .ConfigureAwait(false); if (!versionOptions.IsLatest && !string.IsNullOrWhiteSpace(versionOptions.CommitHash)) { await PrerequisiteHelper .RunGit( - new[] { "checkout", versionOptions.CommitHash }, + ["checkout", versionOptions.CommitHash], progress?.AsProcessOutputHandler(), installLocation ) diff --git a/StabilityMatrix.Core/Models/Packages/Cogstudio.cs b/StabilityMatrix.Core/Models/Packages/Cogstudio.cs index 4f088ff1..a030dbb7 100644 --- a/StabilityMatrix.Core/Models/Packages/Cogstudio.cs +++ b/StabilityMatrix.Core/Models/Packages/Cogstudio.cs @@ -16,8 +16,17 @@ public class Cogstudio( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "Cogstudio"; public override string DisplayName { get; set; } = "Cogstudio"; @@ -156,7 +165,7 @@ void HandleConsoleOutput(ProcessOutput s) } VenvRunner.RunDetached( - [Path.Combine(installLocation, options.Command ?? LaunchCommand), ..options.Arguments], + [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments], HandleConsoleOutput, OnExit ); diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 8de58582..9e054686 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -25,8 +25,17 @@ public class ComfyUI( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public override string Name => "ComfyUI"; @@ -296,6 +305,13 @@ IPyInstallationManager pyInstallationManager Type = LaunchOptionType.Bool, Options = ["--auto-launch"], }, + new() + { + Name = "Enable Manager", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = ["--enable-manager"], + }, LaunchOptionDefinition.Extras, ]; @@ -474,6 +490,29 @@ await StandardPipInstallProcessAsync( Logger.Error(e, "Failed to verify/update SageAttention after installation"); } + // Install Comfy Manager (built-in to ComfyUI) + try + { + var managerRequirementsFile = Path.Combine(installLocation, "manager_requirements.txt"); + if (File.Exists(managerRequirementsFile)) + { + progress?.Report( + new ProgressReport(-1f, "Installing Comfy Manager requirements...", isIndeterminate: true) + ); + + var pipArgs = new PipInstallArgs().AddArg("-r").AddArg(managerRequirementsFile); + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); + + progress?.Report( + new ProgressReport(-1f, "Comfy Manager installed successfully", isIndeterminate: true) + ); + } + } + catch (Exception e) + { + Logger.Error(e, "Failed to install Comfy Manager requirements"); + } + progress?.Report(new ProgressReport(1, "Install complete", isIndeterminate: false)); } @@ -772,26 +811,41 @@ private async Task InstallTritonAndSageAttention(InstalledPackage? installedPack if (installedPackage?.FullPath is null) return; - var installSageStep = new InstallSageAttentionStep( - DownloadService, - PrerequisiteHelper, - PyInstallationManager - ) - { - InstalledPackage = installedPackage, - WorkingDirectory = new DirectoryPath(installedPackage.FullPath), - EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables, - IsBlackwellGpu = - SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu(), - }; - var runner = new PackageModificationRunner { ShowDialogOnStart = true, ModificationCompleteMessage = "Triton and SageAttention installed successfully", }; EventManager.Instance.OnPackageInstallProgressAdded(runner); - await runner.ExecuteSteps([installSageStep]).ConfigureAwait(false); + + await runner + .ExecuteSteps( + [ + new ActionPackageStep( + async progress => + { + await using var venvRunner = await SetupVenvPure( + installedPackage.FullPath, + pythonVersion: PyVersion.Parse(installedPackage.PythonVersion) + ) + .ConfigureAwait(false); + + var gpuInfo = + SettingsManager.Settings.PreferredGpu + ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia); + + await PipWheelService + .InstallTritonAsync(venvRunner, progress) + .ConfigureAwait(false); + await PipWheelService + .InstallSageAttentionAsync(venvRunner, gpuInfo, progress) + .ConfigureAwait(false); + }, + "Installing Triton and SageAttention" + ), + ] + ) + .ConfigureAwait(false); if (runner.Failed) return; @@ -837,24 +891,38 @@ private async Task InstallNunchaku(InstalledPackage? installedPackage) if (installedPackage?.FullPath is null) return; - var installNunchaku = new InstallNunchakuStep(PyInstallationManager) - { - InstalledPackage = installedPackage, - WorkingDirectory = new DirectoryPath(installedPackage.FullPath), - EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables, - PreferredGpu = - SettingsManager.Settings.PreferredGpu - ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia || x.IsAmd), - ComfyExtensionManager = ExtensionManager, - }; - var runner = new PackageModificationRunner { ShowDialogOnStart = true, ModificationCompleteMessage = "Nunchaku installed successfully", }; EventManager.Instance.OnPackageInstallProgressAdded(runner); - await runner.ExecuteSteps([installNunchaku]).ConfigureAwait(false); + + await runner + .ExecuteSteps( + [ + new ActionPackageStep( + async progress => + { + await using var venvRunner = await SetupVenvPure( + installedPackage.FullPath, + pythonVersion: PyVersion.Parse(installedPackage.PythonVersion) + ) + .ConfigureAwait(false); + + var gpuInfo = + SettingsManager.Settings.PreferredGpu + ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia || x.IsAmd); + + await PipWheelService + .InstallNunchakuAsync(venvRunner, gpuInfo, progress) + .ConfigureAwait(false); + }, + "Installing Nunchaku" + ), + ] + ) + .ConfigureAwait(false); } private ImmutableDictionary GetEnvVars(ImmutableDictionary env) diff --git a/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs b/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs index 723db36b..8cee6ce8 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyZluda.cs @@ -21,8 +21,17 @@ public class ComfyZluda( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : ComfyUI(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : ComfyUI( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private const string ZludaPatchDownloadUrl = "https://github.com/lshqqytiger/ZLUDA/releases/download/rel.5e717459179dc272b7d7d23391f0fad66c7459cf/ZLUDA-nightly-windows-rocm6-amd64.zip"; @@ -93,9 +102,7 @@ public override List LaunchOptions }, }; - options.AddRange( - base.LaunchOptions.Where(x => x.Name != "Cross Attention Method") - ); + options.AddRange(base.LaunchOptions.Where(x => x.Name != "Cross Attention Method")); return options; } } diff --git a/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs b/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs index ccc44c34..b8929fb6 100644 --- a/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs +++ b/StabilityMatrix.Core/Models/Packages/DankDiffusion.cs @@ -15,9 +15,17 @@ public DankDiffusion( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService ) - : base(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) { } + : base( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { } public override string Name => "dank-diffusion"; public override string DisplayName { get; set; } = "Dank Diffusion"; diff --git a/StabilityMatrix.Core/Models/Packages/FluxGym.cs b/StabilityMatrix.Core/Models/Packages/FluxGym.cs index dfb9d074..977cd278 100644 --- a/StabilityMatrix.Core/Models/Packages/FluxGym.cs +++ b/StabilityMatrix.Core/Models/Packages/FluxGym.cs @@ -17,8 +17,17 @@ public class FluxGym( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "FluxGym"; public override string DisplayName { get; set; } = "FluxGym"; diff --git a/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs b/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs index 8b3a79d5..ef465f0a 100644 --- a/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs +++ b/StabilityMatrix.Core/Models/Packages/FocusControlNet.cs @@ -12,8 +12,17 @@ public class FocusControlNet( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : Fooocus( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "Fooocus-ControlNet-SDXL"; public override string DisplayName { get; set; } = "Fooocus-ControlNet"; diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs index b2c0a602..0972e33e 100644 --- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs @@ -18,8 +18,17 @@ public class Fooocus( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "Fooocus"; public override string DisplayName { get; set; } = "Fooocus"; @@ -29,6 +38,7 @@ IPyInstallationManager pyInstallationManager public override string LicenseType => "GPL-3.0"; public override string LicenseUrl => "https://github.com/lllyasviel/Fooocus/blob/main/LICENSE"; public override string LaunchCommand => "launch.py"; + public override PackageType PackageType => PackageType.Legacy; public override Uri PreviewImageUri => new( diff --git a/StabilityMatrix.Core/Models/Packages/FooocusMre.cs b/StabilityMatrix.Core/Models/Packages/FooocusMre.cs index a0b264b4..653c4109 100644 --- a/StabilityMatrix.Core/Models/Packages/FooocusMre.cs +++ b/StabilityMatrix.Core/Models/Packages/FooocusMre.cs @@ -17,8 +17,17 @@ public class FooocusMre( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "Fooocus-MRE"; public override string DisplayName { get; set; } = "Fooocus-MRE"; diff --git a/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs b/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs index 9c1a2632..e07a8c8e 100644 --- a/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs +++ b/StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs @@ -20,8 +20,17 @@ public class ForgeAmdGpu( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : SDWebForge( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "stable-diffusion-webui-amdgpu-forge"; public override string DisplayName => "Stable Diffusion WebUI AMDGPU Forge"; @@ -42,7 +51,7 @@ IPyInstallationManager pyInstallationManager public override bool IsCompatible => HardwareHelper.PreferDirectMLOrZluda(); - public override PackageType PackageType => PackageType.SdInference; + public override PackageType PackageType => PackageType.Legacy; public override IEnumerable Prerequisites => base.Prerequisites.Concat([PackagePrerequisite.HipSdk]); diff --git a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs index 1aaa2869..a8887587 100644 --- a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs +++ b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs @@ -1,9 +1,9 @@ -using System.Text; -using Injectio.Attributes; +using Injectio.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Python; @@ -17,8 +17,17 @@ public class ForgeClassic( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : SDWebForge( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "forge-classic"; public override string Author => "Haoming02"; @@ -32,11 +41,23 @@ IPyInstallationManager pyInstallationManager "https://github.com/Haoming02/sd-webui-forge-classic/blob/classic/LICENSE"; public override Uri PreviewImageUri => new("https://github.com/Haoming02/sd-webui-forge-classic/raw/classic/html/ui.webp"); - public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Recommended; + public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended; public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda]; public override bool IsCompatible => HardwareHelper.HasNvidiaGpu(); public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13; - public override PackageType PackageType => PackageType.SdInference; + public override PackageType PackageType => PackageType.Legacy; + + public override Dictionary> SharedOutputFolders => + new() + { + [SharedOutputType.Extras] = ["output/extras-images"], + [SharedOutputType.Saved] = ["output/images"], + [SharedOutputType.Img2Img] = ["output/img2img-images"], + [SharedOutputType.Text2Img] = ["output/txt2img-images"], + [SharedOutputType.Img2ImgGrids] = ["output/img2img-grids"], + [SharedOutputType.Text2ImgGrids] = ["output/txt2img-grids"], + [SharedOutputType.SVD] = ["output/videos"], + }; public override List LaunchOptions => [ @@ -101,7 +122,7 @@ IPyInstallationManager pyInstallationManager Name = "Auto Launch", Type = LaunchOptionType.Bool, Description = "Set whether to auto launch the webui", - Options = { "--auto-launch" }, + Options = { "--autolaunch" }, }, new() { @@ -136,6 +157,24 @@ IPyInstallationManager pyInstallationManager [SharedFolderType.DiffusionModels] = ["models/Stable-diffusion/unet"], }; + public override List GetExtraCommands() + { + var commands = new List(); + + if (Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true) + { + commands.Add( + new ExtraPackageCommand + { + CommandName = "Install Triton and SageAttention", + Command = InstallTritonAndSageAttention, + } + ); + } + + return commands; + } + public override async Task InstallPackage( string installLocation, InstalledPackage installedPackage, @@ -152,46 +191,123 @@ public override async Task InstallPackage( ) .ConfigureAwait(false); - // Dynamically discover all requirements files - var requirementsPaths = new List { "requirements.txt" }; - var extensionsBuiltinDir = new DirectoryPath(installLocation, "extensions-builtin"); - if (extensionsBuiltinDir.Exists) + progress?.Report(new ProgressReport(-1f, "Running install script...", isIndeterminate: true)); + + // Build args for their launch.py - use --uv for fast installs, --exit to quit after setup + var launchArgs = new List { "launch.py", "--uv", "--exit" }; + + // For Ampere or newer GPUs, enable sage attention, flash attention, and nunchaku + var isAmpereOrNewer = + SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() + ?? HardwareHelper.IterGpuInfo().Any(x => x.IsNvidia && x.IsAmpereOrNewerGpu()); + + if (isAmpereOrNewer) { - requirementsPaths.AddRange( - extensionsBuiltinDir - .EnumerateFiles("requirements.txt", EnumerationOptionConstants.AllDirectories) - .Select(f => Path.GetRelativePath(installLocation, f.ToString())) + launchArgs.Add("--sage"); + launchArgs.Add("--flash"); + launchArgs.Add("--nunchaku"); + } + + // Run their install script with our venv Python + venvRunner.WorkingDirectory = new DirectoryPath(installLocation); + venvRunner.RunDetached([.. launchArgs], onConsoleOutput); + + await venvRunner.Process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + + if (venvRunner.Process.ExitCode != 0) + { + throw new InvalidOperationException( + $"Install script failed with exit code {venvRunner.Process.ExitCode}" ); } - var isLegacyNvidia = - SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu(); + progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); + } + + private async Task InstallTritonAndSageAttention(InstalledPackage? installedPackage) + { + if (installedPackage?.FullPath is null) + return; - var config = new PipInstallConfig + var runner = new PackageModificationRunner { - RequirementsFilePaths = requirementsPaths, - TorchVersion = "<2.9.0", - TorchvisionVersion = "<0.24.0", - CudaIndex = isLegacyNvidia ? "cu126" : "cu128", - UpgradePackages = true, - ExtraPipArgs = - [ - "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip", - ], - PostInstallPipArgs = ["numpy==1.26.4"], + ShowDialogOnStart = true, + ModificationCompleteMessage = "Triton and SageAttention installed successfully", }; + EventManager.Instance.OnPackageInstallProgressAdded(runner); + + await runner + .ExecuteSteps( + [ + new ActionPackageStep( + async progress => + { + await using var venvRunner = await SetupVenvPure( + installedPackage.FullPath, + pythonVersion: PyVersion.Parse(installedPackage.PythonVersion) + ) + .ConfigureAwait(false); + + var gpuInfo = + SettingsManager.Settings.PreferredGpu + ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia); + + var tritonVersion = Compat.IsWindows ? "3.5.1.post22" : "3.5.1"; - await StandardPipInstallProcessAsync( - venvRunner, - options, - installedPackage, - config, - onConsoleOutput, - progress, - cancellationToken + await PipWheelService + .InstallTritonAsync(venvRunner, progress, tritonVersion) + .ConfigureAwait(false); + await PipWheelService + .InstallSageAttentionAsync(venvRunner, gpuInfo, progress, "2.2.0") + .ConfigureAwait(false); + }, + "Installing Triton and SageAttention" + ), + ] ) .ConfigureAwait(false); - progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); + if (runner.Failed) + return; + + await using var transaction = settingsManager.BeginTransaction(); + var packageInSettings = transaction.Settings.InstalledPackages.FirstOrDefault(x => + x.Id == installedPackage.Id + ); + + if (packageInSettings is null) + return; + + var attentionOptions = packageInSettings.LaunchArgs?.Where(opt => + opt.Name.Contains("attention", StringComparison.OrdinalIgnoreCase) + ); + + if (attentionOptions is not null) + { + foreach (var option in attentionOptions) + { + option.OptionValue = false; + } + } + + var sageAttention = packageInSettings.LaunchArgs?.FirstOrDefault(opt => + opt.Name.Contains("sage", StringComparison.OrdinalIgnoreCase) + ); + + if (sageAttention is not null) + { + sageAttention.OptionValue = true; + } + else + { + packageInSettings.LaunchArgs?.Add( + new LaunchOption + { + Name = "--sage", + Type = LaunchOptionType.Bool, + OptionValue = true, + } + ); + } } } diff --git a/StabilityMatrix.Core/Models/Packages/ForgeNeo.cs b/StabilityMatrix.Core/Models/Packages/ForgeNeo.cs index f73be910..8c5d65fc 100644 --- a/StabilityMatrix.Core/Models/Packages/ForgeNeo.cs +++ b/StabilityMatrix.Core/Models/Packages/ForgeNeo.cs @@ -12,11 +12,23 @@ public class ForgeNeo( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : ForgeClassic(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : ForgeClassic( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "forge-neo"; public override string DisplayName { get; set; } = "Stable Diffusion WebUI Forge - Neo"; public override string MainBranch => "neo"; public override PackageType PackageType => PackageType.SdInference; + + public override string Blurb => + "Neo mainly serves as an continuation for the \"latest\" version of Forge. Additionally, this fork is focused on optimization and usability, with the main goal of being the lightest WebUI without any bloatwares."; } diff --git a/StabilityMatrix.Core/Models/Packages/FramePack.cs b/StabilityMatrix.Core/Models/Packages/FramePack.cs index c80ed2eb..32f4eed6 100644 --- a/StabilityMatrix.Core/Models/Packages/FramePack.cs +++ b/StabilityMatrix.Core/Models/Packages/FramePack.cs @@ -18,8 +18,17 @@ public class FramePack( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "framepack"; public override string DisplayName { get; set; } = "FramePack"; diff --git a/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs b/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs index 55ea5c02..72640e7d 100644 --- a/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs +++ b/StabilityMatrix.Core/Models/Packages/FramePackStudio.cs @@ -19,8 +19,17 @@ public class FramePackStudio( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : FramePack(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : FramePack( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "framepack-studio"; public override string DisplayName { get; set; } = "FramePack Studio"; diff --git a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs index ed7ac484..bf510a78 100644 --- a/StabilityMatrix.Core/Models/Packages/InvokeAI.cs +++ b/StabilityMatrix.Core/Models/Packages/InvokeAI.cs @@ -25,8 +25,17 @@ public class InvokeAI( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private const string RelativeRootPath = "invokeai-root"; diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs index 7c061f7f..eee7305f 100644 --- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs +++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs @@ -18,8 +18,17 @@ public class KohyaSs( IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, IPyRunner runner, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "kohya_ss"; public override string DisplayName { get; set; } = "kohya_ss"; diff --git a/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs b/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs index 7cb92841..f8993507 100644 --- a/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs @@ -12,8 +12,17 @@ public class Mashb1tFooocus( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : Fooocus( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "mashb1t-fooocus"; public override string Author => "mashb1t"; diff --git a/StabilityMatrix.Core/Models/Packages/OneTrainer.cs b/StabilityMatrix.Core/Models/Packages/OneTrainer.cs index bc330fa3..80c4e38b 100644 --- a/StabilityMatrix.Core/Models/Packages/OneTrainer.cs +++ b/StabilityMatrix.Core/Models/Packages/OneTrainer.cs @@ -17,8 +17,17 @@ public class OneTrainer( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "OneTrainer"; public override string DisplayName { get; set; } = "OneTrainer"; diff --git a/StabilityMatrix.Core/Models/Packages/Reforge.cs b/StabilityMatrix.Core/Models/Packages/Reforge.cs index 71be0f84..ff642905 100644 --- a/StabilityMatrix.Core/Models/Packages/Reforge.cs +++ b/StabilityMatrix.Core/Models/Packages/Reforge.cs @@ -12,8 +12,17 @@ public class Reforge( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : SDWebForge(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : SDWebForge( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "reforge"; public override string Author => "Panchovix"; diff --git a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs index dd3fb220..51c276b2 100644 --- a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs @@ -16,8 +16,17 @@ public class RuinedFooocus( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : Fooocus( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "RuinedFooocus"; public override string DisplayName { get; set; } = "RuinedFooocus"; diff --git a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs index 09d33024..d0fcac13 100644 --- a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs +++ b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs @@ -18,8 +18,17 @@ public class SDWebForge( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : A3WebUI(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : A3WebUI( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "stable-diffusion-webui-forge"; public override string DisplayName { get; set; } = "Stable Diffusion WebUI Forge"; @@ -36,7 +45,7 @@ IPyInstallationManager pyInstallationManager public override string MainBranch => "main"; public override bool ShouldIgnoreReleases => true; public override IPackageExtensionManager ExtensionManager => null; - public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended; + public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple; public override PackageType PackageType => PackageType.Legacy; public override List LaunchOptions => diff --git a/StabilityMatrix.Core/Models/Packages/Sdfx.cs b/StabilityMatrix.Core/Models/Packages/Sdfx.cs index d7153c1a..fe185dd2 100644 --- a/StabilityMatrix.Core/Models/Packages/Sdfx.cs +++ b/StabilityMatrix.Core/Models/Packages/Sdfx.cs @@ -21,8 +21,17 @@ public class Sdfx( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "sdfx"; public override string DisplayName { get; set; } = "SDFX"; diff --git a/StabilityMatrix.Core/Models/Packages/SimpleSDXL.cs b/StabilityMatrix.Core/Models/Packages/SimpleSDXL.cs index 3e748f1d..d9c12f0b 100644 --- a/StabilityMatrix.Core/Models/Packages/SimpleSDXL.cs +++ b/StabilityMatrix.Core/Models/Packages/SimpleSDXL.cs @@ -16,8 +16,17 @@ public class SimpleSDXL( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : Fooocus(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : Fooocus( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "SimpleSDXL"; public override string DisplayName { get; set; } = "SimpleSDXL"; @@ -52,7 +61,7 @@ IPyInstallationManager pyInstallationManager PublishedDate = DateTimeOffset.Parse("2025-01-11"), InfoUrl = new Uri("https://github.com/metercai/SimpleSDXL/issues/97"), AffectedVersions = ["*"], // Affects all versions - } + }, ]; public override List LaunchOptions => @@ -68,7 +77,7 @@ IPyInstallationManager pyInstallationManager "--preset realistic", "--preset Flux", "--preset Kolors", - "--preset pony_v6" + "--preset pony_v6", }, }, new() @@ -77,28 +86,28 @@ IPyInstallationManager pyInstallationManager Type = LaunchOptionType.String, Description = "Translate UI using json files in [language] folder.", InitialValue = "en", - Options = { "--language" } + Options = { "--language" }, }, new() { Name = "Port", Type = LaunchOptionType.String, Description = "Sets the listen port", - Options = { "--port" } + Options = { "--port" }, }, new() { Name = "Share", Type = LaunchOptionType.Bool, Description = "Set whether to share on Gradio", - Options = { "--share" } + Options = { "--share" }, }, new() { Name = "Listen", Type = LaunchOptionType.String, Description = "Set the listen interface", - Options = { "--listen" } + Options = { "--listen" }, }, new() { @@ -106,7 +115,7 @@ IPyInstallationManager pyInstallationManager Description = "Disables downloading models for presets", DefaultValue = false, Type = LaunchOptionType.Bool, - Options = { "--disable-preset-download" } + Options = { "--disable-preset-download" }, }, new() { @@ -114,7 +123,7 @@ IPyInstallationManager pyInstallationManager Description = "Launches the UI with light or dark theme", Type = LaunchOptionType.String, DefaultValue = "dark", - Options = { "--theme" } + Options = { "--theme" }, }, new() { @@ -123,28 +132,28 @@ IPyInstallationManager pyInstallationManager "Force loading models to vram when the unload can be avoided. Some Mac users may need this.", Type = LaunchOptionType.Bool, InitialValue = Compat.IsMacOS, - Options = { "--disable-offload-from-vram" } + Options = { "--disable-offload-from-vram" }, }, new() { Name = "Disable image log", Description = "Prevent writing images and logs to the outputs folder.", Type = LaunchOptionType.Bool, - Options = { "--disable-image-log" } + Options = { "--disable-image-log" }, }, new() { Name = "Disable metadata", Description = "Disables saving metadata to images.", Type = LaunchOptionType.Bool, - Options = { "--disable-metadata" } + Options = { "--disable-metadata" }, }, new() { Name = "Disable enhance output sorting", Description = "Disables enhance output sorting for final image gallery.", Type = LaunchOptionType.Bool, - Options = { "--disable-enhance-output-sorting" } + Options = { "--disable-enhance-output-sorting" }, }, new() { @@ -152,7 +161,7 @@ IPyInstallationManager pyInstallationManager Description = "Enables automatic description of uov and enhance image when prompt is empty", DefaultValue = true, Type = LaunchOptionType.Bool, - Options = { "--enable-auto-describe-image" } + Options = { "--enable-auto-describe-image" }, }, new() { @@ -160,16 +169,16 @@ IPyInstallationManager pyInstallationManager Description = "Always download newer models.", DefaultValue = false, Type = LaunchOptionType.Bool, - Options = { "--always-download-new-model" } + Options = { "--always-download-new-model" }, }, new() { Name = "Disable comfyd", Description = "Disable auto start comfyd server at launch", Type = LaunchOptionType.Bool, - Options = { "--disable-comfyd" } + Options = { "--disable-comfyd" }, }, - LaunchOptionDefinition.Extras + LaunchOptionDefinition.Extras, ]; public override async Task InstallPackage( diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs index 6b4ee0de..2f5c5309 100644 --- a/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs +++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs @@ -17,8 +17,17 @@ public class StableDiffusionDirectMl( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : A3WebUI(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : A3WebUI( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -51,7 +60,7 @@ public override List LaunchOptions Name = "Use DirectML", Type = LaunchOptionType.Bool, InitialValue = HardwareHelper.PreferDirectMLOrZluda(), - Options = ["--use-directml"] + Options = ["--use-directml"], } ); diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs index 0c98b643..4017ec6f 100644 --- a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs +++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs @@ -21,8 +21,17 @@ public class StableDiffusionUx( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs index af83af17..d71e52f5 100644 --- a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs +++ b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs @@ -22,8 +22,17 @@ public class StableSwarm( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private Process? dotnetProcess; diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 7e56f30a..09cc94b8 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -23,8 +23,17 @@ public class VladAutomatic( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -40,7 +49,7 @@ IPyInstallationManager pyInstallationManager public override bool ShouldIgnoreReleases => true; public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink; - public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert; + public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced; public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10; public override IEnumerable AvailableTorchIndices => diff --git a/StabilityMatrix.Core/Models/Packages/VoltaML.cs b/StabilityMatrix.Core/Models/Packages/VoltaML.cs index df3368ca..4168f2e2 100644 --- a/StabilityMatrix.Core/Models/Packages/VoltaML.cs +++ b/StabilityMatrix.Core/Models/Packages/VoltaML.cs @@ -16,8 +16,17 @@ public class VoltaML( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "voltaML-fast-stable-diffusion"; public override string DisplayName { get; set; } = "VoltaML"; diff --git a/StabilityMatrix.Core/Models/Packages/Wan2GP.cs b/StabilityMatrix.Core/Models/Packages/Wan2GP.cs index fe2db5db..2b2cf3c8 100644 --- a/StabilityMatrix.Core/Models/Packages/Wan2GP.cs +++ b/StabilityMatrix.Core/Models/Packages/Wan2GP.cs @@ -29,13 +29,21 @@ public class Wan2GP( ISettingsManager settingsManager, IDownloadService downloadService, IPrerequisiteHelper prerequisiteHelper, - IPyInstallationManager pyInstallationManager -) : BaseGitPackage(githubApi, settingsManager, downloadService, prerequisiteHelper, pyInstallationManager) + IPyInstallationManager pyInstallationManager, + IPipWheelService pipWheelService +) + : BaseGitPackage( + githubApi, + settingsManager, + downloadService, + prerequisiteHelper, + pyInstallationManager, + pipWheelService + ) { public override string Name => "Wan2GP"; public override string DisplayName { get; set; } = "Wan2GP"; public override string Author => "deepbeepmeep"; - public override string Blurb => "Super Optimized Gradio UI for AI video creation for GPU poor machines (6GB+ VRAM). " + "Supports Wan 2.1/2.2, Qwen, Hunyuan Video, LTX Video and Flux."; @@ -54,12 +62,14 @@ IPyInstallationManager pyInstallationManager public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda, TorchIndex.Rocm]; - public override bool IsCompatible => HardwareHelper.HasNvidiaGpu() || HardwareHelper.HasAmdGpu(); + public override bool IsCompatible => + HardwareHelper.HasNvidiaGpu() + || (Compat.IsWindows ? HardwareHelper.HasWindowsRocmSupportedGpu() : HardwareHelper.HasAmdGpu()); public override string MainBranch => "main"; public override bool ShouldIgnoreReleases => true; - public override Dictionary>? SharedOutputFolders => + public override Dictionary> SharedOutputFolders => new() { [SharedOutputType.Img2Vid] = ["outputs"] }; // AMD ROCm requires Python 3.11, NVIDIA uses 3.10 @@ -68,7 +78,11 @@ IPyInstallationManager pyInstallationManager public override string Disclaimer => IsAmdRocm && Compat.IsWindows +<<<<<<< HEAD ? "AMD GPU support on Windows is experimental. Supported GPUs: 7900(XT), 7800(XT), 7600(XT), Phoenix, 9070(XT) and Strix Halo." +======= + ? "AMD GPU support on Windows requires RX 7000 series or newer GPU" +>>>>>>> 1c542602 (Merge pull request #1193 from ionite34/fix-bugs-n-stuff) : string.Empty; /// @@ -83,14 +97,14 @@ IPyInstallationManager pyInstallationManager Name = "Host", Type = LaunchOptionType.String, DefaultValue = "127.0.0.1", - Options = ["--server"], + Options = ["--server-name"], }, new() { Name = "Port", Type = LaunchOptionType.String, DefaultValue = "7860", - Options = ["--port"], + Options = ["--server-port"], }, new() { @@ -100,6 +114,13 @@ IPyInstallationManager pyInstallationManager Options = ["--share"], }, new() + { + Name = "Listen", + Type = LaunchOptionType.Bool, + Description = "Make server accessible on network", + Options = ["--listen"], + }, + new() { Name = "Multiple Images", Type = LaunchOptionType.Bool, @@ -167,27 +188,11 @@ public override async Task InstallPackage( if (torchIndex == TorchIndex.Rocm) { - await InstallAmdRocmAsync( - venvRunner, - installedPackage, - options, - progress, - onConsoleOutput, - cancellationToken - ) - .ConfigureAwait(false); + await InstallAmdRocmAsync(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); } else { - await InstallNvidiaAsync( - venvRunner, - installedPackage, - options, - progress, - onConsoleOutput, - cancellationToken - ) - .ConfigureAwait(false); + await InstallNvidiaAsync(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); } progress?.Report(new ProgressReport(1, "Install complete", isIndeterminate: false)); @@ -195,11 +200,8 @@ await InstallNvidiaAsync( private async Task InstallNvidiaAsync( IPyVenvRunner venvRunner, - InstalledPackage installedPackage, - InstallPackageOptions options, IProgress? progress, - Action? onConsoleOutput, - CancellationToken cancellationToken + Action? onConsoleOutput ) { var isLegacyNvidia = @@ -238,18 +240,17 @@ CancellationToken cancellationToken // Install hf-xet and pin setuptools to avoid distutils compatibility issues with Python 3.10 await venvRunner.PipInstall("hf-xet \"setuptools<70.0.0\"", onConsoleOutput).ConfigureAwait(false); - // Install triton-windows for newer NVIDIA GPUs on Windows - if (Compat.IsWindows && isNewerNvidia) + if (!isNewerNvidia) + return; + + // Install triton n stuff for newer NVIDIA GPUs + if (Compat.IsWindows) { progress?.Report(new ProgressReport(-1f, "Installing triton-windows...", isIndeterminate: true)); await venvRunner .PipInstall("triton-windows==3.3.1.post19", onConsoleOutput) .ConfigureAwait(false); - } - // Install SageAttention and Flash Attention - if (Compat.IsWindows) - { progress?.Report(new ProgressReport(-1f, "Installing SageAttention...", isIndeterminate: true)); await venvRunner .PipInstall( @@ -279,7 +280,7 @@ await venvRunner progress?.Report(new ProgressReport(-1f, "Installing Flash Attention...", isIndeterminate: true)); await venvRunner .PipInstall( - "https://github.com/kingbri1/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu128torch2.7.0cxx11abiFALSE-cp310-cp310-linux_x86_64.whl", + "https://huggingface.co/cocktailpeanut/wheels/resolve/main/flash_attn-2.8.3%2Bcu128torch2.7-cp310-cp310-linux_x86_64.whl", onConsoleOutput ) .ConfigureAwait(false); @@ -290,11 +291,8 @@ await venvRunner private async Task InstallAmdRocmAsync( IPyVenvRunner venvRunner, - InstalledPackage installedPackage, - InstallPackageOptions options, IProgress? progress, - Action? onConsoleOutput, - CancellationToken cancellationToken + Action? onConsoleOutput ) { progress?.Report(new ProgressReport(-1f, "Upgrading pip...", isIndeterminate: true)); @@ -335,11 +333,6 @@ await venvRunner // Install requirements directly using -r flag (handles @ URL syntax properly) progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true)); await venvRunner.PipInstall("-r requirements.txt", onConsoleOutput).ConfigureAwait(false); - - // Install additional packages - await venvRunner - .PipInstall("hf-xet setuptools numpy==1.26.4", onConsoleOutput) - .ConfigureAwait(false); } else { @@ -359,12 +352,10 @@ await venvRunner .AddArg("--no-deps"); await venvRunner.PipInstall(torchArgs, onConsoleOutput).ConfigureAwait(false); - - // Install additional packages - await venvRunner - .PipInstall("hf-xet setuptools numpy==1.26.4", onConsoleOutput) - .ConfigureAwait(false); } + + // Install additional packages + await venvRunner.PipInstall("hf-xet setuptools numpy==1.26.4", onConsoleOutput).ConfigureAwait(false); } public override async Task RunPackage( diff --git a/StabilityMatrix.Core/Python/UvVenvRunner.cs b/StabilityMatrix.Core/Python/UvVenvRunner.cs index c5099c00..53a295ab 100644 --- a/StabilityMatrix.Core/Python/UvVenvRunner.cs +++ b/StabilityMatrix.Core/Python/UvVenvRunner.cs @@ -243,6 +243,8 @@ public async Task PipInstall(ProcessArgs args, Action? outputData throw new FileNotFoundException("uv not found", UvExecutablePath); } + SetPyvenvCfg(BaseInstall.RootPath); + // Record output for errors var output = new StringBuilder(); @@ -282,6 +284,8 @@ public async Task PipUninstall(ProcessArgs args, Action? outputDa throw new FileNotFoundException("uv not found", UvExecutablePath); } + SetPyvenvCfg(BaseInstall.RootPath); + // Record output for errors var output = new StringBuilder(); @@ -319,6 +323,8 @@ public async Task> PipList() throw new FileNotFoundException("uv not found", UvExecutablePath); } + SetPyvenvCfg(BaseInstall.RootPath); + var result = await ProcessRunner .GetProcessResultAsync( UvExecutablePath, @@ -369,6 +375,8 @@ public async Task> PipList() throw new FileNotFoundException("uv not found", UvExecutablePath); } + SetPyvenvCfg(BaseInstall.RootPath); + var result = await ProcessRunner .GetProcessResultAsync( UvExecutablePath, diff --git a/StabilityMatrix.Core/Services/IPipWheelService.cs b/StabilityMatrix.Core/Services/IPipWheelService.cs new file mode 100644 index 00000000..25b90a86 --- /dev/null +++ b/StabilityMatrix.Core/Services/IPipWheelService.cs @@ -0,0 +1,55 @@ +using StabilityMatrix.Core.Helper.HardwareInfo; +using StabilityMatrix.Core.Models.Progress; +using StabilityMatrix.Core.Python; + +namespace StabilityMatrix.Core.Services; + +/// +/// Service for installing pip wheel packages from GitHub releases. +/// All install methods are safe to call regardless of platform/GPU support - +/// they will silently no-op if the package is not applicable. +/// +public interface IPipWheelService +{ + /// + /// Installs Triton. Windows uses triton-windows, Linux uses triton. + /// No-ops on macOS. + /// + Task InstallTritonAsync( + IPyVenvRunner venv, + IProgress? progress = null, + string? version = null + ); + + /// + /// Installs SageAttention from pre-built wheels or source. + /// No-ops on macOS or non-NVIDIA GPUs. + /// + Task InstallSageAttentionAsync( + IPyVenvRunner venv, + GpuInfo? gpuInfo = null, + IProgress? progress = null, + string? version = null + ); + + /// + /// Installs Nunchaku from pre-built wheels. + /// No-ops on macOS or GPUs with compute capability < 7.5. + /// + Task InstallNunchakuAsync( + IPyVenvRunner venv, + GpuInfo? gpuInfo = null, + IProgress? progress = null, + string? version = null + ); + + /// + /// Installs FlashAttention from pre-built wheels. + /// Windows only. No-ops on Linux/macOS. + /// + Task InstallFlashAttentionAsync( + IPyVenvRunner venv, + IProgress? progress = null, + string? version = null + ); +} diff --git a/StabilityMatrix.Core/Services/ModelIndexService.cs b/StabilityMatrix.Core/Services/ModelIndexService.cs index b1830af5..c6874d37 100644 --- a/StabilityMatrix.Core/Services/ModelIndexService.cs +++ b/StabilityMatrix.Core/Services/ModelIndexService.cs @@ -28,7 +28,6 @@ public partial class ModelIndexService : IModelIndexService private readonly ISettingsManager settingsManager; private readonly ILiteDbContext liteDbContext; private readonly ModelFinder modelFinder; - private readonly SemaphoreSlim safetensorMetadataParseLock = new(1, 1); private DateTimeOffset lastUpdateCheck = DateTimeOffset.MinValue; @@ -565,86 +564,6 @@ await liteDbContext ); EventManager.Instance.OnModelIndexChanged(); - - Task.Run(LoadSafetensorMetadataAsync) - .SafeFireAndForget(ex => - { - logger.LogError(ex, "Error loading safetensor metadata"); - }); - } - - private async Task LoadSafetensorMetadataAsync() - { - if (!settingsManager.IsLibraryDirSet) - { - logger.LogTrace("Safetensor metadata loading skipped, library directory not set"); - return; - } - - if (new DirectoryPath(settingsManager.ModelsDirectory) is not { Exists: true } modelsDir) - { - logger.LogTrace("Safetensor metadata loading skipped, model directory does not exist"); - return; - } - - await safetensorMetadataParseLock.WaitAsync().ConfigureAwait(false); - try - { - var stopwatch = Stopwatch.StartNew(); - var readSuccess = 0; - var readFail = 0; - logger.LogInformation("Loading safetensor metadata..."); - - var models = ModelIndex - .Values.SelectMany(x => x) - .Where(m => !m.SafetensorMetadataParsed && m.RelativePath.EndsWith(".safetensors")); - - await Parallel - .ForEachAsync( - models, - new ParallelOptions - { - MaxDegreeOfParallelism = Math.Max(1, Math.Min(Environment.ProcessorCount / 2, 6)), - TaskScheduler = TaskScheduler.Default, - }, - async (model, token) => - { - if (model.SafetensorMetadataParsed) - return; - - if (!model.RelativePath.EndsWith(".safetensors")) - return; - - try - { - var safetensorPath = model.GetFullPath(modelsDir); - var metadata = await SafetensorMetadata - .ParseAsync(safetensorPath) - .ConfigureAwait(false); - model.SafetensorMetadata = metadata; - model.SafetensorMetadataParsed = true; - - Interlocked.Increment(ref readSuccess); - } - catch - { - Interlocked.Increment(ref readFail); - } - } - ) - .ConfigureAwait(false); - - logger.LogInformation( - "Loaded safetensor metadata for {Success} models, failed to load for {Fail} models in {Time:F2}ms", - readSuccess, - readFail, - stopwatch.Elapsed.TotalMilliseconds - ); - } - finally - { - safetensorMetadataParseLock.Release(); - } } /// diff --git a/StabilityMatrix.Core/Services/PipWheelService.cs b/StabilityMatrix.Core/Services/PipWheelService.cs new file mode 100644 index 00000000..e549667c --- /dev/null +++ b/StabilityMatrix.Core/Services/PipWheelService.cs @@ -0,0 +1,588 @@ +using System.Text.RegularExpressions; +using Injectio.Attributes; +using NLog; +using Octokit; +using StabilityMatrix.Core.Extensions; +using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; +using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Models.Progress; +using StabilityMatrix.Core.Processes; +using StabilityMatrix.Core.Python; + +namespace StabilityMatrix.Core.Services; + +/// +/// Service for installing pip wheel packages from GitHub releases. +/// +[RegisterSingleton] +public class PipWheelService( + IGithubApiCache githubApi, + IDownloadService downloadService, + IPrerequisiteHelper prerequisiteHelper +) : IPipWheelService +{ + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + #region Triton + + /// + public async Task InstallTritonAsync( + IPyVenvRunner venv, + IProgress? progress = null, + string? version = null + ) + { + // No-op on macOS + if (Compat.IsMacOS) + { + Logger.Info("Skipping Triton installation - not supported on macOS"); + return; + } + + var packageName = Compat.IsWindows ? "triton-windows" : "triton"; + var versionSpec = string.IsNullOrWhiteSpace(version) ? "" : $"=={version}"; + + progress?.Report(new ProgressReport(-1f, $"Installing {packageName}", isIndeterminate: true)); + + await venv.PipInstall($"{packageName}{versionSpec}", progress.AsProcessOutputHandler()) + .ConfigureAwait(false); + + progress?.Report(new ProgressReport(1f, "Triton installed", isIndeterminate: false)); + } + + #endregion + + #region SageAttention + + /// + public async Task InstallSageAttentionAsync( + IPyVenvRunner venv, + GpuInfo? gpuInfo = null, + IProgress? progress = null, + string? version = null + ) + { + // No-op on macOS + if (Compat.IsMacOS) + { + Logger.Info("Skipping SageAttention installation - not supported on macOS"); + return; + } + + // No-op for non-NVIDIA GPUs (SageAttention requires CUDA) + if (gpuInfo is not null && !gpuInfo.IsNvidia) + { + Logger.Info("Skipping SageAttention installation - requires NVIDIA GPU"); + return; + } + + // On Linux, can use pip directly + if (Compat.IsLinux) + { + var versionSpec = string.IsNullOrWhiteSpace(version) ? "" : $"=={version}"; + progress?.Report(new ProgressReport(-1f, "Installing SageAttention", isIndeterminate: true)); + await venv.PipInstall($"sageattention{versionSpec}", progress.AsProcessOutputHandler()) + .ConfigureAwait(false); + progress?.Report(new ProgressReport(1f, "SageAttention installed", isIndeterminate: false)); + return; + } + + // Windows: find wheel from GitHub releases + await InstallSageAttentionWindowsAsync(venv, gpuInfo, progress, version).ConfigureAwait(false); + } + + private async Task InstallSageAttentionWindowsAsync( + IPyVenvRunner venv, + GpuInfo? gpuInfo, + IProgress? progress, + string? version + ) + { + var torchInfo = await venv.PipShow("torch").ConfigureAwait(false); + if (torchInfo is null) + { + Logger.Warn("Cannot install SageAttention - torch not installed"); + return; + } + + progress?.Report(new ProgressReport(-1f, "Finding SageAttention wheel", isIndeterminate: true)); + + // Get releases from GitHub + var releases = await githubApi.GetAllReleases("woct0rdho", "SageAttention").ConfigureAwait(false); + var releaseList = releases + .Where(r => r.TagName.Contains("windows")) + .OrderByDescending(r => r.CreatedAt) + .ToList(); + + if (releaseList.Count == 0) + { + Logger.Warn("No SageAttention Windows releases found"); + await InstallSageAttentionFromSourceAsync(venv, progress).ConfigureAwait(false); + return; + } + + // Find matching wheel from release assets + var wheelUrl = FindMatchingWheelAsset(releaseList, torchInfo, venv.Version, version); + + if (!string.IsNullOrWhiteSpace(wheelUrl)) + { + progress?.Report( + new ProgressReport(-1f, "Installing Triton & SageAttention", isIndeterminate: true) + ); + + // Install triton-windows first, then sage with --no-deps to prevent torch reinstall + var pipArgs = new PipInstallArgs("triton-windows").AddArg("--no-deps").AddArg(wheelUrl); + await venv.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false); + + progress?.Report(new ProgressReport(1f, "SageAttention installed", isIndeterminate: false)); + return; + } + + // No wheel found - fall back to building from source + Logger.Info("No matching SageAttention wheel found, building from source"); + await InstallSageAttentionFromSourceAsync(venv, progress).ConfigureAwait(false); + } + + private static string? FindMatchingWheelAsset( + IEnumerable releases, + PipShowResult torchInfo, + PyVersion pyVersion, + string? targetVersion + ) + { + // Parse torch info + var torchVersionStr = torchInfo.Version; + var plusIndex = torchVersionStr.IndexOf('+'); + var baseTorchVersion = plusIndex >= 0 ? torchVersionStr[..plusIndex] : torchVersionStr; + var cudaIndex = plusIndex >= 0 ? torchVersionStr[(plusIndex + 1)..] : ""; + + // Get major.minor of torch + var torchParts = baseTorchVersion.Split('.'); + var shortTorch = torchParts.Length >= 2 ? $"{torchParts[0]}.{torchParts[1]}" : baseTorchVersion; + + // Get python version string (e.g., "cp312") + var shortPy = $"cp3{pyVersion.Minor}"; + + foreach (var release in releases) + { + // If a specific version is requested, filter releases + if (!string.IsNullOrWhiteSpace(targetVersion) && !release.TagName.Contains(targetVersion)) + continue; + + foreach (var asset in release.Assets) + { + var name = asset.Name; + + // Must be a wheel file + if (!name.EndsWith(".whl")) + continue; + + // Must be for Windows + if (!name.Contains("win_amd64")) + continue; + + // Check Python version compatibility (cp39-abi3 works for cp39+, or specific version) + var matchesPython = + name.Contains($"{shortPy}-{shortPy}") + || name.Contains("cp39-abi3") + || (pyVersion.Minor >= 9 && name.Contains("abi3")); + + if (!matchesPython) + continue; + + // Check torch version match + // Assets use patterns like: cu128torch2.9.0 or cu130torch2.9.0andhigher + var matchesTorch = + name.Contains($"torch{shortTorch}") + || name.Contains($"torch{baseTorchVersion}") + || (name.Contains("andhigher") && CompareTorchVersions(baseTorchVersion, name)); + + // Check CUDA index match + var matchesCuda = !string.IsNullOrEmpty(cudaIndex) && name.Contains(cudaIndex); + + if (matchesTorch && matchesCuda) + { + Logger.Info("Found matching SageAttention wheel: {Name}", name); + return asset.BrowserDownloadUrl; + } + } + } + + return null; + } + + private static bool CompareTorchVersions(string installedTorch, string assetName) + { + // Extract torch version from asset name (e.g., "torch2.9.0andhigher" -> "2.9.0") + var match = Regex.Match(assetName, @"torch(\d+\.\d+\.\d+)"); + if (!match.Success) + return false; + + if (!Version.TryParse(installedTorch, out var installed)) + return false; + + if (!Version.TryParse(match.Groups[1].Value, out var required)) + return false; + + // "andhigher" means installed version must be >= required version + return installed >= required; + } + + private async Task InstallSageAttentionFromSourceAsync( + IPyVenvRunner venv, + IProgress? progress + ) + { + // Check prerequisites + if (!prerequisiteHelper.IsVcBuildToolsInstalled) + { + Logger.Warn("Cannot build SageAttention from source - VS Build Tools not installed"); + return; + } + + var nvccPath = await Utilities.WhichAsync("nvcc").ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(nvccPath)) + { + var cuda126Path = new DirectoryPath( + @"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.6\bin" + ); + var cuda128Path = new DirectoryPath( + @"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.8\bin" + ); + + if (!cuda126Path.Exists && !cuda128Path.Exists) + { + Logger.Warn("Cannot build SageAttention from source - CUDA Toolkit not found"); + return; + } + + nvccPath = cuda128Path.Exists + ? cuda128Path.JoinFile("nvcc.exe").ToString() + : cuda126Path.JoinFile("nvcc.exe").ToString(); + } + + // Set up CUDA environment + var cudaBinPath = Path.GetDirectoryName(nvccPath)!; + var cudaHome = Path.GetDirectoryName(cudaBinPath)!; + + venv.UpdateEnvironmentVariables(env => + { + env = env.TryGetValue("PATH", out var pathValue) + ? env.SetItem("PATH", $"{cudaBinPath}{Path.PathSeparator}{pathValue}") + : env.Add("PATH", cudaBinPath); + + if (!env.ContainsKey("CUDA_HOME")) + { + env = env.Add("CUDA_HOME", cudaHome); + } + + return env; + }); + + progress?.Report(new ProgressReport(-1f, "Installing Triton", isIndeterminate: true)); + await venv.PipInstall("triton-windows", progress.AsProcessOutputHandler()).ConfigureAwait(false); + + venv.UpdateEnvironmentVariables(env => env.SetItem("SETUPTOOLS_USE_DISTUTILS", "setuptools")); + + // Download python libs for building + await AddMissingLibsToVenvAsync(venv, progress).ConfigureAwait(false); + + var sageDir = venv.WorkingDirectory?.JoinDir("SageAttention") ?? new DirectoryPath("SageAttention"); + + if (!sageDir.Exists) + { + progress?.Report(new ProgressReport(-1f, "Downloading SageAttention", isIndeterminate: true)); + await prerequisiteHelper + .RunGit( + ["clone", "https://github.com/thu-ml/SageAttention.git", sageDir.ToString()], + progress.AsProcessOutputHandler() + ) + .ConfigureAwait(false); + } + + progress?.Report(new ProgressReport(-1f, "Building SageAttention", isIndeterminate: true)); + await venv.PipInstall([sageDir.ToString()], progress.AsProcessOutputHandler()).ConfigureAwait(false); + + progress?.Report(new ProgressReport(1f, "SageAttention built and installed", isIndeterminate: false)); + } + + private async Task AddMissingLibsToVenvAsync(IPyVenvRunner venv, IProgress? progress) + { + var venvLibsDir = venv.RootPath.JoinDir("libs"); + var venvIncludeDir = venv.RootPath.JoinDir("include"); + + if ( + venvLibsDir.Exists + && venvIncludeDir.Exists + && venvLibsDir.JoinFile("python3.lib").Exists + && venvLibsDir.JoinFile("python310.lib").Exists + ) + { + return; + } + + const string pythonLibsUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip"; + var downloadPath = venv.RootPath.JoinFile("python_libs_for_sage.zip"); + + progress?.Report(new ProgressReport(-1f, "Downloading Python libraries", isIndeterminate: true)); + await downloadService + .DownloadToFileAsync(pythonLibsUrl, downloadPath, progress) + .ConfigureAwait(false); + + progress?.Report(new ProgressReport(-1f, "Extracting Python libraries", isIndeterminate: true)); + await ArchiveHelper.Extract7Z(downloadPath, venv.RootPath, progress).ConfigureAwait(false); + + var includeFolder = venv.RootPath.JoinDir("include"); + var scriptsIncludeFolder = venv.RootPath.JoinDir("Scripts", "include"); + await includeFolder.CopyToAsync(scriptsIncludeFolder).ConfigureAwait(false); + + await downloadPath.DeleteAsync().ConfigureAwait(false); + } + + #endregion + + #region Nunchaku + + /// + public async Task InstallNunchakuAsync( + IPyVenvRunner venv, + GpuInfo? gpuInfo = null, + IProgress? progress = null, + string? version = null + ) + { + // No-op on macOS + if (Compat.IsMacOS) + { + Logger.Info("Skipping Nunchaku installation - not supported on macOS"); + return; + } + + // No-op for GPUs with compute capability < 7.5 + if (gpuInfo?.ComputeCapabilityValue is < 7.5m) + { + Logger.Info("Skipping Nunchaku installation - GPU compute capability < 7.5"); + return; + } + + var torchInfo = await venv.PipShow("torch").ConfigureAwait(false); + if (torchInfo is null) + { + Logger.Warn("Cannot install Nunchaku - torch not installed"); + return; + } + + progress?.Report(new ProgressReport(-1f, "Finding Nunchaku wheel", isIndeterminate: true)); + + // Get releases from GitHub + var releases = await githubApi.GetAllReleases("nunchaku-ai", "nunchaku").ConfigureAwait(false); + var releaseList = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.CreatedAt).ToList(); + + if (releaseList.Count == 0) + { + Logger.Warn("No Nunchaku releases found"); + return; + } + + var wheelUrl = FindMatchingNunchakuWheelAsset(releaseList, torchInfo, venv.Version, version); + + if (string.IsNullOrWhiteSpace(wheelUrl)) + { + Logger.Warn("No compatible Nunchaku wheel found for torch {TorchVersion}", torchInfo.Version); + return; + } + + progress?.Report(new ProgressReport(-1f, "Installing Nunchaku", isIndeterminate: true)); + // Use --no-deps to prevent reinstalling torch without CUDA + await venv.PipInstall( + new PipInstallArgs("--no-deps").AddArg(wheelUrl), + progress.AsProcessOutputHandler() + ) + .ConfigureAwait(false); + progress?.Report(new ProgressReport(1f, "Nunchaku installed", isIndeterminate: false)); + } + + private static string? FindMatchingNunchakuWheelAsset( + IEnumerable releases, + PipShowResult torchInfo, + PyVersion pyVersion, + string? targetVersion + ) + { + // Parse torch version + var torchVersionStr = torchInfo.Version; + var plusIndex = torchVersionStr.IndexOf('+'); + var baseTorchVersion = plusIndex >= 0 ? torchVersionStr[..plusIndex] : torchVersionStr; + var torchParts = baseTorchVersion.Split('.'); + var shortTorch = torchParts.Length >= 2 ? $"{torchParts[0]}.{torchParts[1]}" : baseTorchVersion; + + // Get python version string + var shortPy = $"cp3{pyVersion.Minor}"; + + // Get platform + var platform = Compat.IsWindows ? "win_amd64" : "linux_x86_64"; + + Logger.Debug( + "Searching for Nunchaku wheel: Python={ShortPy}, Torch={ShortTorch}, Platform={Platform}", + shortPy, + shortTorch, + platform + ); + + foreach (var release in releases) + { + // If a specific version is requested, filter releases + if (!string.IsNullOrWhiteSpace(targetVersion) && !release.TagName.Contains(targetVersion)) + continue; + + foreach (var asset in release.Assets) + { + var name = asset.Name; + + if (!name.EndsWith(".whl")) + continue; + + if (!name.Contains(platform)) + continue; + + // Check Python version + if (!name.Contains($"{shortPy}-{shortPy}")) + continue; + + // Check torch version (assets use patterns like: torch2.7 or torch2.8) + if (!name.Contains($"torch{shortTorch}")) + continue; + + Logger.Info( + "Found matching Nunchaku wheel: {Name} (Python={ShortPy}, Torch={ShortTorch})", + name, + shortPy, + shortTorch + ); + return asset.BrowserDownloadUrl; + } + } + + return null; + } + + #endregion + + #region FlashAttention + + /// + public async Task InstallFlashAttentionAsync( + IPyVenvRunner venv, + IProgress? progress = null, + string? version = null + ) + { + // Windows only + if (!Compat.IsWindows) + { + Logger.Info("Skipping FlashAttention installation - Windows only"); + return; + } + + var torchInfo = await venv.PipShow("torch").ConfigureAwait(false); + if (torchInfo is null) + { + Logger.Warn("Cannot install FlashAttention - torch not installed"); + return; + } + + progress?.Report(new ProgressReport(-1f, "Finding FlashAttention wheel", isIndeterminate: true)); + + // Get releases from GitHub + var releases = await githubApi + .GetAllReleases("mjun0812", "flash-attention-prebuild-wheels") + .ConfigureAwait(false); + var releaseList = releases.OrderByDescending(r => r.CreatedAt).ToList(); + + if (releaseList.Count == 0) + { + Logger.Warn("No FlashAttention releases found"); + return; + } + + var wheelUrl = FindMatchingFlashAttentionWheelAsset(releaseList, torchInfo, venv.Version, version); + + if (string.IsNullOrWhiteSpace(wheelUrl)) + { + Logger.Warn( + "No compatible FlashAttention wheel found for torch {TorchVersion}", + torchInfo.Version + ); + return; + } + + progress?.Report(new ProgressReport(-1f, "Installing FlashAttention", isIndeterminate: true)); + // Use --no-deps to prevent reinstalling torch without CUDA + await venv.PipInstall( + new PipInstallArgs("--no-deps").AddArg(wheelUrl), + progress.AsProcessOutputHandler() + ) + .ConfigureAwait(false); + progress?.Report(new ProgressReport(1f, "FlashAttention installed", isIndeterminate: false)); + } + + private static string? FindMatchingFlashAttentionWheelAsset( + IEnumerable releases, + PipShowResult torchInfo, + PyVersion pyVersion, + string? targetVersion + ) + { + // Parse torch version and CUDA index + var torchVersionStr = torchInfo.Version; + var plusIndex = torchVersionStr.IndexOf('+'); + var baseTorchVersion = plusIndex >= 0 ? torchVersionStr[..plusIndex] : torchVersionStr; + var cudaIndex = plusIndex >= 0 ? torchVersionStr[(plusIndex + 1)..] : ""; + var torchParts = baseTorchVersion.Split('.'); + var shortTorch = torchParts.Length >= 2 ? $"{torchParts[0]}.{torchParts[1]}" : baseTorchVersion; + + // Get python version string + var shortPy = $"cp3{pyVersion.Minor}"; + + foreach (var release in releases) + { + foreach (var asset in release.Assets) + { + var name = asset.Name; + + if (!name.EndsWith(".whl")) + continue; + + if (!name.Contains("win_amd64")) + continue; + + // Check for specific version if requested + if ( + !string.IsNullOrWhiteSpace(targetVersion) && !name.Contains($"flash_attn-{targetVersion}") + ) + continue; + + // Check Python version + if (!name.Contains($"{shortPy}-{shortPy}")) + continue; + + // Check torch version + if (!name.Contains($"torch{shortTorch}")) + continue; + + // Check CUDA index + if (!string.IsNullOrEmpty(cudaIndex) && !name.Contains(cudaIndex)) + continue; + + Logger.Info("Found matching FlashAttention wheel: {Name}", name); + return asset.BrowserDownloadUrl; + } + } + + return null; + } + + #endregion +} diff --git a/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs b/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs index 03719e93..f7802703 100644 --- a/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs +++ b/StabilityMatrix.Tests/Helper/PackageFactoryTests.cs @@ -12,8 +12,20 @@ public class PackageFactoryTests [TestInitialize] public void Setup() { - fakeBasePackages = new List { new DankDiffusion(null!, null!, null!, null!, null!) }; - packageFactory = new PackageFactory(fakeBasePackages, null!, null!, null!, null!, null!, null!); + fakeBasePackages = new List + { + new DankDiffusion(null!, null!, null!, null!, null!, null!), + }; + packageFactory = new PackageFactory( + fakeBasePackages, + null!, + null!, + null!, + null!, + null!, + null!, + null! + ); } [TestMethod] diff --git a/StabilityMatrix.Tests/Models/Packages/PackageHelper.cs b/StabilityMatrix.Tests/Models/Packages/PackageHelper.cs index 4a41e756..b165031d 100644 --- a/StabilityMatrix.Tests/Models/Packages/PackageHelper.cs +++ b/StabilityMatrix.Tests/Models/Packages/PackageHelper.cs @@ -23,7 +23,8 @@ public static IEnumerable GetPackages() .AddSingleton(Substitute.For()) .AddSingleton(Substitute.For()) .AddSingleton(Substitute.For()) - .AddSingleton(Substitute.For()); + .AddSingleton(Substitute.For()) + .AddSingleton(Substitute.For()); var assembly = typeof(BasePackage).Assembly; var packageTypes = assembly From 4142dd9d50ed9efe7208c3ff84dfb09451d97684 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 26 Jan 2026 11:15:24 -0800 Subject: [PATCH 04/14] fix merge conflict --- StabilityMatrix.Core/Models/Packages/Wan2GP.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/Wan2GP.cs b/StabilityMatrix.Core/Models/Packages/Wan2GP.cs index 2b2cf3c8..52f50d69 100644 --- a/StabilityMatrix.Core/Models/Packages/Wan2GP.cs +++ b/StabilityMatrix.Core/Models/Packages/Wan2GP.cs @@ -78,11 +78,7 @@ IPipWheelService pipWheelService public override string Disclaimer => IsAmdRocm && Compat.IsWindows -<<<<<<< HEAD - ? "AMD GPU support on Windows is experimental. Supported GPUs: 7900(XT), 7800(XT), 7600(XT), Phoenix, 9070(XT) and Strix Halo." -======= ? "AMD GPU support on Windows requires RX 7000 series or newer GPU" ->>>>>>> 1c542602 (Merge pull request #1193 from ionite34/fix-bugs-n-stuff) : string.Empty; /// From 3e694b96fced2671d36d76f9f4e6bca421c058d0 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 8 Feb 2026 21:50:44 -0800 Subject: [PATCH 05/14] fix test --- StabilityMatrix.Tests/Models/Packages/PackageLinkTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StabilityMatrix.Tests/Models/Packages/PackageLinkTests.cs b/StabilityMatrix.Tests/Models/Packages/PackageLinkTests.cs index c0b3113e..d1cf7484 100644 --- a/StabilityMatrix.Tests/Models/Packages/PackageLinkTests.cs +++ b/StabilityMatrix.Tests/Models/Packages/PackageLinkTests.cs @@ -17,7 +17,7 @@ public sealed class PackageLinkTests new() { DefaultRequestHeaders = { { "User-Agent", "StabilityMatrix/2.0" } } }; private static IEnumerable PackagesData => - PackageHelper.GetPackages().Select(p => new object[] { p }); + PackageHelper.GetPackages().Where(x => x is not ComfyZluda).Select(p => new object[] { p }); private static readonly AsyncRetryPolicy RetryPolicy = Policy .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.TooManyRequests) From c97d1c39b4bb80f15642fa03ff63104735114428 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 10 Feb 2026 21:22:42 -0800 Subject: [PATCH 06/14] Merge pull request #1199 from ionite34/more-neo-fixes Update uv to v0.9.30 and adjust Python version requirements for Forge Neo (cherry picked from commit 05891605c448c300815b6d6191858aea76a965ca) # Conflicts: # CHANGELOG.md --- CHANGELOG.md | 9 +- .../Helpers/UnixPrerequisiteHelper.cs | 6 +- .../Helpers/WindowsPrerequisiteHelper.cs | 4 +- .../PackageManager/PackageCardViewModel.cs | 44 ++++ .../Models/Packages/BasePackage.cs | 6 + .../Models/Packages/ForgeClassic.cs | 199 +++++++++++++++++- .../Python/PyInstallationManager.cs | 3 +- 7 files changed, 256 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4cca439..6d8d79a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 ### Changed - Disabled update checking for legacy InvokeAI installations using Python 3.10.11 - Hide rating stars in the Civitai browser page if no rating is available -- Updated uv to v0.9.26 +- Updated uv to v0.9.30 - Updated PortableGit to v2.52.0.windows.1 - Updated Sage/Triton/Nunchaku installers to use GitHub API to fetch latest releases - Updated ComfyUI installations and updates to automatically install ComfyUI Manager @@ -21,10 +21,15 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault! - Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku ### Fixed +<<<<<<< HEAD +======= +- Fixed parsing of escape sequences in Inference such as `\\` +- Fixed batch notification firing when only one image is generated +- Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things +>>>>>>> 05891605 (Merge pull request #1199 from ionite34/more-neo-fixes) - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub - Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model - Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo -- Fixed [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things - Fixed [#1505](https://github.com/LykosAI/StabilityMatrix/issues/1505) - incorrect port argument for Wan2GP - Possibly fix [#1502](https://github.com/LykosAI/StabilityMatrix/issues/1502) - English fonts not displaying correctly on Linux in Chinese environments - Fixed [#1476](https://github.com/LykosAI/StabilityMatrix/issues/1476) - Incorrect shared output folder for Forge Classic/Neo diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index 009d1f56..5eb72176 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -35,9 +35,9 @@ IPyInstallationManager pyInstallationManager private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private const string UvMacDownloadUrl = - "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz"; + "https://github.com/astral-sh/uv/releases/download/0.9.30/uv-aarch64-apple-darwin.tar.gz"; private const string UvLinuxDownloadUrl = - "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz"; + "https://github.com/astral-sh/uv/releases/download/0.9.30/uv-x86_64-unknown-linux-gnu.tar.gz"; private DirectoryPath HomeDir => settingsManager.LibraryDir; private DirectoryPath AssetsDir => HomeDir.JoinDir("Assets"); @@ -75,7 +75,7 @@ private bool IsPythonVersionInstalled(PyVersion version) => // Cached store of whether or not git is installed private bool? isGitInstalled; - private string ExpectedUvVersion => "0.9.26"; + private string ExpectedUvVersion => "0.9.30"; public bool IsVcBuildToolsInstalled => false; public bool IsHipSdkInstalled => false; diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index 2a5ed558..52c61475 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -50,7 +50,7 @@ IPyInstallationManager pyInstallationManager private const string PythonLibsDownloadUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip"; private const string UvWindowsDownloadUrl = - "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip"; + "https://github.com/astral-sh/uv/releases/download/0.9.30/uv-x86_64-pc-windows-msvc.zip"; private string HomeDir => settingsManager.LibraryDir; @@ -116,7 +116,7 @@ private string GetPythonLibraryZipPath(PyVersion version) => private string UvExtractPath => Path.Combine(AssetsDir, "uv"); public string UvExePath => Path.Combine(UvExtractPath, "uv.exe"); public bool IsUvInstalled => File.Exists(UvExePath); - private string ExpectedUvVersion => "0.9.26"; + private string ExpectedUvVersion => "0.9.30"; public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin"); public bool IsVcBuildToolsInstalled => Directory.Exists(VcBuildToolsExistsPath); diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index 1791b786..4b5252ac 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -441,6 +441,9 @@ public async Task Update() return; } + if (!await ShowPythonUpgradeDialogIfNeeded(basePackage, Package)) + return; + var packageName = Package.DisplayName ?? Package.PackageName ?? ""; Text = $"Updating {packageName}"; @@ -590,6 +593,9 @@ private async Task ChangeVersion() return; } + if (!await ShowPythonUpgradeDialogIfNeeded(basePackage, Package)) + return; + var packageName = Package.DisplayName ?? Package.PackageName ?? ""; Text = $"Updating {packageName}"; @@ -989,6 +995,44 @@ private async Task HasUpdate() } } + private static bool RequiresPythonUpgradeNotice( + BasePackage basePackage, + InstalledPackage installedPackage + ) + { + if (basePackage.MinimumPythonVersion is not { } minimumVersion) + return false; + + return PyVersion.TryParse(installedPackage.PythonVersion, out var currentVersion) + && currentVersion < minimumVersion; + } + + private async Task ShowPythonUpgradeDialogIfNeeded( + BasePackage basePackage, + InstalledPackage installedPackage + ) + { + if (!RequiresPythonUpgradeNotice(basePackage, installedPackage)) + return true; + + var dialog = new BetterContentDialog + { + Title = "Python Upgrade Required", + Content = + "This update will recreate the package venv to migrate from Python " + + $"{installedPackage.PythonVersion} to {basePackage.MinimumPythonVersion}.\n\n" + + "Any custom pip packages manually installed into the current venv may need to be reinstalled. " + + "Your launch options, extensions, and generated files are not affected.\n\n" + + "You can also install a fresh copy and migrate manually.\n\n" + + "Continue with update?", + PrimaryButtonText = "Continue", + CloseButtonText = Resources.Action_Cancel, + DefaultButton = ContentDialogButton.Primary, + }; + + return await dialog.ShowAsync() == ContentDialogResult.Primary; + } + public void ToggleSharedModelSymlink() => IsSharedModelSymlink = !IsSharedModelSymlink; public void ToggleSharedModelConfig() => IsSharedModelConfig = !IsSharedModelConfig; diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs index 155ae288..b698ea4f 100644 --- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs @@ -62,6 +62,12 @@ public abstract class BasePackage(ISettingsManager settingsManager) public virtual string? AdminRequiredReason => null; public virtual PyVersion RecommendedPythonVersion => PyInstallationManager.Python_3_10_17; + /// + /// Minimum Python version required for updates. When set, updating a package with a lower + /// installed Python version will prompt for venv recreation. Null means no minimum enforced. + /// + public virtual PyVersion? MinimumPythonVersion => null; + /// /// Returns a list of extra commands that can be executed for this package. /// The function takes an InstalledPackage parameter to operate on a specific installation. diff --git a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs index a8887587..1084eec8 100644 --- a/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs +++ b/StabilityMatrix.Core/Models/Packages/ForgeClassic.cs @@ -1,4 +1,5 @@ using Injectio.Attributes; +using NLog; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.HardwareInfo; @@ -29,6 +30,11 @@ IPipWheelService pipWheelService pipWheelService ) { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private const string LegacyUpgradeAlert = "You are updating from an old version"; + private const string ContinuePrompt = "Press Enter to Continue"; + public override PyVersion? MinimumPythonVersion => Python.PyInstallationManager.Python_3_13_12; + public override string Name => "forge-classic"; public override string Author => "Haoming02"; public override string RepositoryName => "sd-webui-forge-classic"; @@ -44,7 +50,7 @@ IPipWheelService pipWheelService public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended; public override IEnumerable AvailableTorchIndices => [TorchIndex.Cuda]; public override bool IsCompatible => HardwareHelper.HasNvidiaGpu(); - public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13; + public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_13_12; public override PackageType PackageType => PackageType.Legacy; public override Dictionary> SharedOutputFolders => @@ -184,10 +190,33 @@ public override async Task InstallPackage( CancellationToken cancellationToken = default ) { + var requestedPythonVersion = + options.PythonOptions.PythonVersion + ?? ( + PyVersion.TryParse(installedPackage.PythonVersion, out var parsedVersion) + ? parsedVersion + : RecommendedPythonVersion + ); + + var shouldUpgradePython = options.IsUpdate && requestedPythonVersion < MinimumPythonVersion; + var targetPythonVersion = shouldUpgradePython ? MinimumPythonVersion!.Value : requestedPythonVersion; + + if (shouldUpgradePython) + { + onConsoleOutput?.Invoke( + ProcessOutput.FromStdOutLine( + $"Upgrading venv Python from {requestedPythonVersion} to {targetPythonVersion}" + ) + ); + + ResetVenvForPythonUpgrade(installLocation, onConsoleOutput); + } + progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); await using var venvRunner = await SetupVenvPure( installLocation, - pythonVersion: options.PythonOptions.PythonVersion + forceRecreate: shouldUpgradePython, + pythonVersion: targetPythonVersion ) .ConfigureAwait(false); @@ -210,18 +239,174 @@ public override async Task InstallPackage( // Run their install script with our venv Python venvRunner.WorkingDirectory = new DirectoryPath(installLocation); - venvRunner.RunDetached([.. launchArgs], onConsoleOutput); - await venvRunner.Process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + var sawLegacyUpdatePrompt = false; + + var exitCode = await RunInstallScriptWithPromptHandling( + venvRunner, + launchArgs, + onConsoleOutput, + cancellationToken, + onLegacyPromptDetected: () => sawLegacyUpdatePrompt = true + ) + .ConfigureAwait(false); + + // If legacy prompt was detected, back up old config files regardless of exit code. + if (options.IsUpdate && sawLegacyUpdatePrompt) + { + BackupLegacyConfigFiles(installLocation, onConsoleOutput); + + // If it also failed, retry once after the backup. + if (exitCode != 0) + { + onConsoleOutput?.Invoke( + ProcessOutput.FromStdOutLine( + "[ForgeClassic] Retrying install after backing up legacy config files..." + ) + ); + + exitCode = await RunInstallScriptWithPromptHandling( + venvRunner, + launchArgs, + onConsoleOutput, + cancellationToken + ) + .ConfigureAwait(false); + } + } + + if (exitCode != 0) + { + throw new InvalidOperationException($"Install script failed with exit code {exitCode}"); + } + + if ( + !string.Equals( + installedPackage.PythonVersion, + targetPythonVersion.StringValue, + StringComparison.Ordinal + ) + ) + { + installedPackage.PythonVersion = targetPythonVersion.StringValue; + } + + progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); + } + + private async Task RunInstallScriptWithPromptHandling( + IPyVenvRunner venvRunner, + IReadOnlyCollection launchArgs, + Action? onConsoleOutput, + CancellationToken cancellationToken, + Action? onLegacyPromptDetected = null + ) + { + var enterSent = false; - if (venvRunner.Process.ExitCode != 0) + void HandleInstallOutput(ProcessOutput output) { + onConsoleOutput?.Invoke(output); + + var isLegacyPrompt = + output.Text.Contains(LegacyUpgradeAlert, StringComparison.OrdinalIgnoreCase) + || output.Text.Contains(ContinuePrompt, StringComparison.OrdinalIgnoreCase); + + if (!isLegacyPrompt) + return; + + onLegacyPromptDetected?.Invoke(); + + if (enterSent || venvRunner.Process is null || venvRunner.Process.HasExited) + return; + + try + { + venvRunner.Process.StandardInput.WriteLine(); + enterSent = true; + + onConsoleOutput?.Invoke( + ProcessOutput.FromStdOutLine( + "[ForgeClassic] Detected legacy update prompt. Sent Enter automatically." + ) + ); + } + catch (Exception e) + { + Logger.Warn(e, "Failed to auto-submit Enter for Forge Classic update prompt"); + } + } + + venvRunner.RunDetached([.. launchArgs], HandleInstallOutput); + var process = + venvRunner.Process + ?? throw new InvalidOperationException("Failed to start Forge Classic install process"); + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); + return process.ExitCode; + } + + private void ResetVenvForPythonUpgrade(string installLocation, Action? onConsoleOutput) + { + var venvPath = Path.Combine(installLocation, "venv"); + if (!Directory.Exists(venvPath)) + return; + + try + { + Directory.Delete(venvPath, recursive: true); + onConsoleOutput?.Invoke( + ProcessOutput.FromStdOutLine("[ForgeClassic] Removed existing venv before Python upgrade.") + ); + } + catch (Exception e) + { + Logger.Warn(e, "Failed to remove existing venv during Forge Classic Python upgrade"); throw new InvalidOperationException( - $"Install script failed with exit code {venvRunner.Process.ExitCode}" + "Failed to remove existing venv for Python upgrade. Ensure Forge is not running and retry.", + e ); } + } - progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false)); + private void BackupLegacyConfigFiles(string installLocation, Action? onConsoleOutput) + { + BackupLegacyConfigFile(installLocation, "config.json", onConsoleOutput); + BackupLegacyConfigFile(installLocation, "ui-config.json", onConsoleOutput); + } + + private void BackupLegacyConfigFile( + string installLocation, + string fileName, + Action? onConsoleOutput + ) + { + var sourcePath = Path.Combine(installLocation, fileName); + if (!File.Exists(sourcePath)) + return; + + var backupPath = GetBackupPath(sourcePath); + File.Move(sourcePath, backupPath); + + var message = $"[ForgeClassic] Backed up {fileName} to {Path.GetFileName(backupPath)}"; + Logger.Info(message); + onConsoleOutput?.Invoke(ProcessOutput.FromStdOutLine(message)); + } + + private static string GetBackupPath(string sourcePath) + { + var nextPath = sourcePath + ".bak"; + if (!File.Exists(nextPath)) + return nextPath; + + var index = 1; + while (true) + { + nextPath = sourcePath + $".bak.{index}"; + if (!File.Exists(nextPath)) + return nextPath; + + index++; + } } private async Task InstallTritonAndSageAttention(InstalledPackage? installedPackage) diff --git a/StabilityMatrix.Core/Python/PyInstallationManager.cs b/StabilityMatrix.Core/Python/PyInstallationManager.cs index e2f98e32..7ae711d5 100644 --- a/StabilityMatrix.Core/Python/PyInstallationManager.cs +++ b/StabilityMatrix.Core/Python/PyInstallationManager.cs @@ -19,6 +19,7 @@ public class PyInstallationManager(IUvManager uvManager, ISettingsManager settin public static readonly PyVersion Python_3_10_17 = new(3, 10, 17); public static readonly PyVersion Python_3_11_13 = new(3, 11, 13); public static readonly PyVersion Python_3_12_10 = new(3, 12, 10); + public static readonly PyVersion Python_3_13_12 = new(3, 13, 12); /// /// List of preferred/target Python versions StabilityMatrix officially supports. @@ -107,7 +108,7 @@ public async Task> GetAllAvailablePythonsAsync() var allPythons = await uvManager.ListAvailablePythonsAsync().ConfigureAwait(false); Func isSupportedVersion = settingsManager.Settings.ShowAllAvailablePythonVersions ? p => p is { Source: "cpython", Version.Minor: >= 10 } - : p => p is { Source: "cpython", Version.Minor: >= 10 and <= 12 }; + : p => p is { Source: "cpython", Version.Minor: >= 10 and <= 13, Variant: not "freethreaded" }; var filteredPythons = allPythons .Where(isSupportedVersion) From a71504baae72d026e1b7cd3bbfaca8c93f4333aa Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 10 Feb 2026 21:24:37 -0800 Subject: [PATCH 07/14] fix chagenlog fix chagenlog --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d8d79a6..55ac5129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault! - Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku ### Fixed -<<<<<<< HEAD -======= -- Fixed parsing of escape sequences in Inference such as `\\` -- Fixed batch notification firing when only one image is generated - Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things ->>>>>>> 05891605 (Merge pull request #1199 from ionite34/more-neo-fixes) - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub - Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model - Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo From 680089057293edbccb1d245fd33ef3133cedd0d3 Mon Sep 17 00:00:00 2001 From: jt Date: Tue, 10 Feb 2026 21:53:08 -0800 Subject: [PATCH 08/14] backport escape sequence fix in inference prompts & shoutout chagenlog --- CHANGELOG.md | 6 ++++++ .../Assets/ImagePrompt.tmLanguage.json | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ac5129..5e8d7e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault! - Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku ### Fixed +- Fixed parsing of escape sequences in Inference such as `\\` - Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub - Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model @@ -30,6 +31,11 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Fixed [#1476](https://github.com/LykosAI/StabilityMatrix/issues/1476) - Incorrect shared output folder for Forge Classic/Neo - Fixed [#1466](https://github.com/LykosAI/StabilityMatrix/issues/1466) - crash after moving portable install - Fixed [#1445](https://github.com/LykosAI/StabilityMatrix/issues/1445) - Linux app updates not actually updating - thanks to @NeuralFault! +### Supporters +#### 🌟 Visionaries +To our stellar Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your generosity keeps this project thriving and gives us the confidence to tackle the big challenges. Thank you for being the foundation that makes it all possible! +#### 🚀 Pioneers +Shoutout to our incredible Pioneer crew for keeping the momentum going! Thank you to: **Szir777**, **Noah M**, **[USA]TechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Adam**, **Droolguy**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, **robek**, **Ghislain G**, **Phil R**, **Tundra Everquill**, and a warm welcome to our newest Pioneers: **Andrew B**, **snotty**, **Miguel A**, and **SinthCore**! ## v2.15.5 ### Added diff --git a/StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json b/StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json index 73226f16..41d7c00c 100644 --- a/StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json +++ b/StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json @@ -26,7 +26,7 @@ "name": "constant.character.escape.prompt" } }, - "end": "[-+.!(){}\\[\\]<\\>:]", + "end": "[-+.!(){}\\[\\]<\\>:|\\\\]", "endCaptures": { "0": { "name": "constant.character.escape.target.prompt" @@ -35,7 +35,7 @@ "name": "meta.structure.escape.prompt", "patterns": [ { - "match": "[^-+.!(){}\\[\\]<\\>:]", + "match": "[^-+.!(){}\\[\\]<\\>:|\\\\]", "name": "invalid.illegal.escape.prompt" } ] @@ -147,7 +147,7 @@ "4": { "name": "punctuation.separator.variable.prompt" }, - "5" : { + "5": { "name": "constant.numeric" } } @@ -214,7 +214,7 @@ "match": "[^#,:\\[\\]\\(\\)\\<\\> \\\\]+", "name": "meta.embedded" }, - "invalid_reserved" : { + "invalid_reserved": { "name": "invalid.illegal.reserved.prompt", "patterns": [ { From 53c4e8788dc4fe12709a2141dae131825f4d0a13 Mon Sep 17 00:00:00 2001 From: jt Date: Tue, 10 Feb 2026 22:42:23 -0800 Subject: [PATCH 09/14] update chagenlog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e8d7e5b..5c5f765e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Updated gfx110X Windows ROCm nightly index - thanks to @NeuralFault! - Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault! - Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku +- Backslashes can now be escaped in Inference prompts via `\\` ### Fixed -- Fixed parsing of escape sequences in Inference such as `\\` - Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub - Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model From 2dde17c7897256796eb3f51b959a613295c15ad7 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 12 Feb 2026 22:16:41 -0800 Subject: [PATCH 10/14] Merge pull request #1204 from ionite34/more-package-fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update wan2gp logging by adding a wrapper script to capture output in console… (cherry picked from commit dd794264f3e8389ba4672c5923782fb5164c4b68) # Conflicts: # CHANGELOG.md --- CHANGELOG.md | 7 ++ .../Models/Packages/A3WebUI.cs | 5 +- .../Models/Packages/Reforge.cs | 7 +- .../Models/Packages/SDWebForge.cs | 4 +- .../Models/Packages/Wan2GP.cs | 83 ++++++++++++++++++- 5 files changed, 100 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c5f765e..4d22f07e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,14 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku - Backslashes can now be escaped in Inference prompts via `\\` ### Fixed +<<<<<<< HEAD - Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things +======= +- Fixed parsing of escape sequences in Inference such as `\\` +- Fixed batch notification firing when only one image is generated +- Fixed [#1546](https://github.com/LykosAI/StabilityMatrix/issues/1546), [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541) - "No module named 'pkg_resources'" error when installing Automatic1111/Forge/reForge packages +- Fixed [#1545](https://github.com/LykosAI/StabilityMatrix/issues/1545), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things +>>>>>>> dd794264 (Merge pull request #1204 from ionite34/more-package-fixes) - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub - Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model - Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index c35f00aa..a047a82d 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -226,7 +226,7 @@ torchIndex is TorchIndex.Cuda // 1. Configure the entire install process declaratively. var config = new PipInstallConfig { - RequirementsFilePaths = ["requirements_versions.txt"], + RequirementsFilePaths = ["requirements_versions.txt", "setuptools<82"], TorchVersion = torchIndex == TorchIndex.Mps ? "==2.3.1" : (isBlackwell ? "" : "==2.1.2"), TorchvisionVersion = torchIndex == TorchIndex.Mps ? "==0.18.1" : (isBlackwell ? "" : "==0.16.2"), XformersVersion = isBlackwell ? " " : "==0.0.23.post1", @@ -235,6 +235,7 @@ torchIndex is TorchIndex.Cuda ExtraPipArgs = [ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip", + "setuptools<82", ], }; @@ -306,7 +307,7 @@ void HandleConsoleOutput(ProcessOutput s) public override IReadOnlyList ExtraLaunchArguments => settingsManager.IsLibraryDirSet ? ["--gradio-allowed-path", settingsManager.ImagesDirectory] : []; - private ImmutableDictionary GetEnvVars(ImmutableDictionary env) + protected virtual ImmutableDictionary GetEnvVars(ImmutableDictionary env) { // Set the Stable Diffusion repository URL to a working fork // This is required because the original Stability-AI/stablediffusion repo was removed diff --git a/StabilityMatrix.Core/Models/Packages/Reforge.cs b/StabilityMatrix.Core/Models/Packages/Reforge.cs index ff642905..d7937d2f 100644 --- a/StabilityMatrix.Core/Models/Packages/Reforge.cs +++ b/StabilityMatrix.Core/Models/Packages/Reforge.cs @@ -1,4 +1,5 @@ -using Injectio.Attributes; +using System.Collections.Immutable; +using Injectio.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Python; @@ -36,4 +37,8 @@ IPipWheelService pipWheelService public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Recommended; public override bool OfferInOneClickInstaller => true; public override PackageType PackageType => PackageType.SdInference; + + protected override ImmutableDictionary GetEnvVars( + ImmutableDictionary env + ) => env; } diff --git a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs index 8bbae4e4..8403ea5c 100644 --- a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs +++ b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs @@ -189,7 +189,7 @@ torchIndex is TorchIndex.Cuda var config = new PipInstallConfig { - PrePipInstallArgs = ["joblib"], + PrePipInstallArgs = ["joblib", "setuptools<82"], RequirementsFilePaths = requirementsPaths, TorchVersion = "", TorchvisionVersion = "", @@ -199,7 +199,7 @@ torchIndex is TorchIndex.Cuda [ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip", ], - PostInstallPipArgs = ["numpy==1.26.4"], + PostInstallPipArgs = ["numpy==1.26.4", "setuptools<82"], }; await StandardPipInstallProcessAsync( diff --git a/StabilityMatrix.Core/Models/Packages/Wan2GP.cs b/StabilityMatrix.Core/Models/Packages/Wan2GP.cs index 52f50d69..2a00a626 100644 --- a/StabilityMatrix.Core/Models/Packages/Wan2GP.cs +++ b/StabilityMatrix.Core/Models/Packages/Wan2GP.cs @@ -86,6 +86,78 @@ IPipWheelService pipWheelService /// private bool IsAmdRocm => GetRecommendedTorchVersion() == TorchIndex.Rocm; + /// + /// Python wrapper script that patches logging to also print to stdout/stderr, so + /// StabilityMatrix can capture the output. Wan2GP logs through Gradio UI notifications + /// (gr.Info/Warning/Error) and callback-driven UI updates that never reach the console. + /// This script: + /// 1. Configures Python's logging module to output to stderr (captures library logging) + /// 2. Prevents transformers from suppressing its own logging (wgp.py calls set_verbosity_error) + /// 3. Monkey-patches gr.Info/Warning/Error to also print to stdout/stderr + /// 4. Runs the target script (wgp.py) via runpy + /// + private const string GradioLogPatchScript = """ + # StabilityMatrix: Patch logging to print to console for capture. + import sys + import logging + + def _apply_logging_patch(): + # Configure Python's root logger to output to stderr at INFO level. + # Many libraries (torch, diffusers, transformers, etc.) use the logging + # module but output may be suppressed without a handler configured. + root = logging.getLogger() + if not any(isinstance(h, logging.StreamHandler) for h in root.handlers): + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(logging.Formatter("[%(name)s] %(levelname)s: %(message)s")) + root.addHandler(handler) + if root.level > logging.INFO: + root.setLevel(logging.INFO) + + # Prevent transformers from suppressing its own logging. + # wgp.py calls transformers.utils.logging.set_verbosity_error() which + # silences all non-error messages. We neutralize those calls so model + # loading and download messages remain visible. + try: + import transformers.utils.logging as tf_logging + tf_logging.set_verbosity_error = lambda: None + tf_logging.set_verbosity_warning = lambda: None + tf_logging.set_verbosity(logging.INFO) + except Exception as e: + print(f"[StabilityMatrix] Failed to patch transformers logging: {e}", file=sys.stderr, flush=True) + + # Monkey-patch Gradio's UI notification functions to also print to console. + # These only fire for validation/error messages, not generation progress. + try: + import gradio as gr + _orig_info = getattr(gr, 'Info', None) + _orig_warning = getattr(gr, 'Warning', None) + _orig_error = getattr(gr, 'Error', None) + if _orig_info is not None: + def patched_info(message, *args, **kwargs): + print(f"[Gradio] {message}", flush=True) + return _orig_info(message, *args, **kwargs) + gr.Info = patched_info + if _orig_warning is not None: + def patched_warning(message, *args, **kwargs): + print(f"[Gradio] WARNING: {message}", flush=True) + return _orig_warning(message, *args, **kwargs) + gr.Warning = patched_warning + if _orig_error is not None: + def patched_error(message, *args, **kwargs): + print(f"[Gradio] ERROR: {message}", file=sys.stderr, flush=True) + return _orig_error(message, *args, **kwargs) + gr.Error = patched_error + except Exception as e: + print(f"[StabilityMatrix] Failed to patch Gradio logging: {e}", file=sys.stderr, flush=True) + + if __name__ == "__main__": + _apply_logging_patch() + target_script = sys.argv[1] + sys.argv = sys.argv[1:] + import runpy + runpy.run_path(target_script, run_name="__main__") + """; + public override List LaunchOptions => [ new() @@ -368,13 +440,22 @@ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage // Fix for distutils compatibility issue with Python 3.10 and setuptools VenvRunner.UpdateEnvironmentVariables(env => env.SetItem("SETUPTOOLS_USE_DISTUTILS", "stdlib")); + // Write the Gradio logging patch wrapper script so gr.Info/Warning/Error + // messages are also printed to stdout/stderr for console capture + var patchScriptPath = Path.Combine(installLocation, "_sm_gradio_log_patch.py"); + await File.WriteAllTextAsync(patchScriptPath, GradioLogPatchScript, cancellationToken) + .ConfigureAwait(false); + + var targetScript = Path.Combine(installLocation, options.Command ?? LaunchCommand); + // Notify user that the package is starting (loading can take a while) onConsoleOutput?.Invoke( new ProcessOutput { Text = "Launching Wan2GP, please wait while the UI initializes...\n" } ); + // Launch via the patch wrapper, which monkey-patches Gradio then runs wgp.py VenvRunner.RunDetached( - [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments], + [patchScriptPath, targetScript, .. options.Arguments], HandleConsoleOutput, OnExit ); From 2d4a4a49916c2b45a2cee10524fb0c83f85526d4 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 12 Feb 2026 22:35:35 -0800 Subject: [PATCH 11/14] fix chagenlog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d22f07e..d362011e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,14 +22,10 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku - Backslashes can now be escaped in Inference prompts via `\\` ### Fixed -<<<<<<< HEAD -- Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things -======= - Fixed parsing of escape sequences in Inference such as `\\` - Fixed batch notification firing when only one image is generated - Fixed [#1546](https://github.com/LykosAI/StabilityMatrix/issues/1546), [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541) - "No module named 'pkg_resources'" error when installing Automatic1111/Forge/reForge packages - Fixed [#1545](https://github.com/LykosAI/StabilityMatrix/issues/1545), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things ->>>>>>> dd794264 (Merge pull request #1204 from ionite34/more-package-fixes) - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub - Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model - Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo From f461c91403eca46022660cc5d807ae34959da573 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 13 Feb 2026 19:55:29 -0800 Subject: [PATCH 12/14] Merge pull request #1206 from ionite34/fix-pkg-resources-for-real Fix the missing pkg_resources error for real this time (cherry picked from commit d9a25ac26919aa0ee00d0fe4f2e6f43ce9b86d82) --- StabilityMatrix.Core/Models/Packages/A3WebUI.cs | 3 +-- StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs | 8 ++++++++ StabilityMatrix.Core/Models/Packages/SDWebForge.cs | 6 ++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index a047a82d..345505bf 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -226,7 +226,7 @@ torchIndex is TorchIndex.Cuda // 1. Configure the entire install process declaratively. var config = new PipInstallConfig { - RequirementsFilePaths = ["requirements_versions.txt", "setuptools<82"], + RequirementsFilePaths = ["requirements_versions.txt"], TorchVersion = torchIndex == TorchIndex.Mps ? "==2.3.1" : (isBlackwell ? "" : "==2.1.2"), TorchvisionVersion = torchIndex == TorchIndex.Mps ? "==0.18.1" : (isBlackwell ? "" : "==0.16.2"), XformersVersion = isBlackwell ? " " : "==0.0.23.post1", @@ -235,7 +235,6 @@ torchIndex is TorchIndex.Cuda ExtraPipArgs = [ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip", - "setuptools<82", ], }; diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index 1976f4f5..bb401a64 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -235,6 +235,14 @@ await PyInstallationManager.GetInstallationAsync(pythonVersion.Value).ConfigureA await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false); } + // Constrain setuptools<82 in uv's isolated build environments. + // setuptools 82+ removed pkg_resources, breaking source builds that import it. + var buildConstraintsPath = Path.Combine(installedPackagePath, venvName, "uv-build-constraints.txt"); + await File.WriteAllTextAsync(buildConstraintsPath, "setuptools<82\n").ConfigureAwait(false); + venvRunner.UpdateEnvironmentVariables(env => + env.SetItem("UV_BUILD_CONSTRAINT", buildConstraintsPath) + ); + // ensure pip is installed await venvRunner.PipInstall("pip", onConsoleOutput).ConfigureAwait(false); diff --git a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs index 8403ea5c..d2675c43 100644 --- a/StabilityMatrix.Core/Models/Packages/SDWebForge.cs +++ b/StabilityMatrix.Core/Models/Packages/SDWebForge.cs @@ -185,11 +185,9 @@ public override async Task InstallPackage( torchIndex is TorchIndex.Cuda && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu()); - var isAmd = torchIndex is TorchIndex.Rocm; - var config = new PipInstallConfig { - PrePipInstallArgs = ["joblib", "setuptools<82"], + PrePipInstallArgs = ["joblib"], RequirementsFilePaths = requirementsPaths, TorchVersion = "", TorchvisionVersion = "", @@ -199,7 +197,7 @@ torchIndex is TorchIndex.Cuda [ "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip", ], - PostInstallPipArgs = ["numpy==1.26.4", "setuptools<82"], + PostInstallPipArgs = ["numpy==1.26.4"], }; await StandardPipInstallProcessAsync( From 86cf2119c32a26782ab782265d6dce2b0f3b172d Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 14 Feb 2026 02:24:39 -0800 Subject: [PATCH 13/14] Merge pull request #1208 from ionite34/fix-the-fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use relative path for UV_BUILD_CONSTRAINT to avoid issues with spaces… (cherry picked from commit c162a79487d68ba315f70f7db5c4e32030a3076a) --- StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs index bb401a64..86761f9f 100644 --- a/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs @@ -239,8 +239,12 @@ await PyInstallationManager.GetInstallationAsync(pythonVersion.Value).ConfigureA // setuptools 82+ removed pkg_resources, breaking source builds that import it. var buildConstraintsPath = Path.Combine(installedPackagePath, venvName, "uv-build-constraints.txt"); await File.WriteAllTextAsync(buildConstraintsPath, "setuptools<82\n").ConfigureAwait(false); + // Use relative path because uv splits UV_BUILD_CONSTRAINT on spaces (it's a list-type env var), + // which breaks when the absolute path contains spaces. The working directory is installedPackagePath, + // so the relative path resolves correctly. + var relativeBuildConstraintsPath = Path.Combine(venvName, "uv-build-constraints.txt"); venvRunner.UpdateEnvironmentVariables(env => - env.SetItem("UV_BUILD_CONSTRAINT", buildConstraintsPath) + env.SetItem("UV_BUILD_CONSTRAINT", relativeBuildConstraintsPath) ); // ensure pip is installed From 8359c05aeb4cbab34d1983254b4632a030109151 Mon Sep 17 00:00:00 2001 From: jt Date: Sat, 14 Feb 2026 16:40:14 -0800 Subject: [PATCH 14/14] Update chagenlog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d362011e..673a9096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,6 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Backslashes can now be escaped in Inference prompts via `\\` ### Fixed - Fixed parsing of escape sequences in Inference such as `\\` -- Fixed batch notification firing when only one image is generated - Fixed [#1546](https://github.com/LykosAI/StabilityMatrix/issues/1546), [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541) - "No module named 'pkg_resources'" error when installing Automatic1111/Forge/reForge packages - Fixed [#1545](https://github.com/LykosAI/StabilityMatrix/issues/1545), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things - Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub @@ -38,7 +37,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 #### 🌟 Visionaries To our stellar Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your generosity keeps this project thriving and gives us the confidence to tackle the big challenges. Thank you for being the foundation that makes it all possible! #### 🚀 Pioneers -Shoutout to our incredible Pioneer crew for keeping the momentum going! Thank you to: **Szir777**, **Noah M**, **[USA]TechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Adam**, **Droolguy**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, **robek**, **Ghislain G**, **Phil R**, **Tundra Everquill**, and a warm welcome to our newest Pioneers: **Andrew B**, **snotty**, **Miguel A**, and **SinthCore**! +Shoutout to our incredible Pioneer crew for keeping the momentum going! Thank you to: **Szir777**, **Noah M**, **[USA]TechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Adam**, **Droolguy**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, **robek**, **Ghislain G**, **Phil R**, **Tundra Everquill**, and a warm welcome to our newest Pioneers: **Andrew B**, **snotty**, **Miguel A**, **SinthCore**, and **Ahmed S**! ## v2.15.5 ### Added