diff --git a/CHANGELOG.md b/CHANGELOG.md index 48096ad6..ef2412c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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). +## v2.7.2 +### Changed +- Changed Symlink shared folder link targets for Automatic1111 and ComfyUI. From `ControlNet -> models/controlnet` to `ControlNet -> models/controlnet/ControlNet` and `T2IAdapter -> models/controlnet/T2IAdapter`. +- Changed FreeU defaults to match recommended SD1.5 defaults +- Changed default denoise strength from 1.0 to 0.7 +### Fixed +- Fixed ControlNet / T2IAdapter shared folder links for Automatic1111 conflicting with each other +- Fixed URIScheme registration errors on Linux +- Fixed RuinedFooocus missing output folder on startup +- Fixed incorrect Fooocus VRAM launch arguments + ## v2.7.1 ### Added - Added Turkish UI language option, thanks to Progesor for the translation diff --git a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs index e500a1e4..75dd4a5f 100644 --- a/StabilityMatrix.Avalonia/Helpers/UriHandler.cs +++ b/StabilityMatrix.Avalonia/Helpers/UriHandler.cs @@ -56,8 +56,6 @@ public class UriHandler Environment.Exit(0); } - public void Callback() { } - public void RegisterUriScheme() { if (Compat.IsWindows) @@ -66,7 +64,17 @@ public class UriHandler } else { - RegisterUriSchemeUnix(); + // Try to register on unix but ignore errors + // Library does not support some distros + try + { + RegisterUriSchemeUnix(); + } + catch (Exception e) + { + Debug.WriteLine(e); + Console.WriteLine(e); + } } } @@ -92,11 +100,7 @@ public class UriHandler private void RegisterUriSchemeUnix() { - var service = URISchemeServiceFactory.GetURISchemeSerivce( - Scheme, - Description, - Compat.AppCurrentPath.FullPath - ); + var service = URISchemeServiceFactory.GetURISchemeSerivce(Scheme, Description, Compat.AppCurrentPath.FullPath); service.Set(); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs index 4080af7a..1aac1239 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs @@ -14,21 +14,25 @@ public partial class FreeUCardViewModel : LoadableViewModelBase public const string ModuleKey = "FreeU"; [ObservableProperty] + [NotifyDataErrorInfo] [Required] [Range(0D, 10D)] - private double b1 = 1.1; + private double b1 = 1.5; [ObservableProperty] + [NotifyDataErrorInfo] [Required] [Range(0D, 10D)] - private double b2 = 1.2; + private double b2 = 1.6; [ObservableProperty] + [NotifyDataErrorInfo] [Required] [Range(0D, 10D)] private double s1 = 0.9; [ObservableProperty] + [NotifyDataErrorInfo] [Required] [Range(0D, 10D)] private double s2 = 0.2; diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs index eeaf4a25..32640d8e 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs @@ -42,7 +42,7 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo private bool isDenoiseStrengthEnabled; [ObservableProperty] - private double denoiseStrength = 1; + private double denoiseStrength = 0.7f; [ObservableProperty] [property: Category("Settings")] diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index 971d7c9a..c1263426 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -89,9 +89,7 @@ public partial class MainWindow : AppWindowBase { base.OnApplyTemplate(e); - navigationService.SetFrame( - FrameView ?? throw new NullReferenceException("Frame not found") - ); + navigationService.SetFrame(FrameView ?? throw new NullReferenceException("Frame not found")); } protected override void OnOpened(EventArgs e) @@ -134,16 +132,15 @@ public partial class MainWindow : AppWindowBase return; // Navigate to first page - Dispatcher.UIThread.Post( - () => - navigationService.NavigateTo( - vm.Pages[0], - new BetterSlideNavigationTransition - { - Effect = SlideNavigationTransitionEffect.FromBottom - } - ) - ); + Dispatcher + .UIThread + .Post( + () => + navigationService.NavigateTo( + vm.Pages[0], + new BetterSlideNavigationTransition { Effect = SlideNavigationTransitionEffect.FromBottom } + ) + ); // Check show update teaching tip if (vm.UpdateViewModel.IsUpdateAvailable) @@ -167,27 +164,28 @@ public partial class MainWindow : AppWindowBase { var mainViewModel = (MainWindowViewModel)DataContext!; - mainViewModel.SelectedCategory = mainViewModel.Pages + mainViewModel.SelectedCategory = mainViewModel + .Pages .Concat(mainViewModel.FooterPages) .FirstOrDefault(x => x.GetType() == e.ViewModelType); } private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo) { - Dispatcher.UIThread.Post(() => - { - var vm = DataContext as MainWindowViewModel; - - if (vm!.ShouldShowUpdateAvailableTeachingTip(updateInfo)) + Dispatcher + .UIThread + .Post(() => { - var target = this.FindControl("FooterUpdateItem")!; - var tip = this.FindControl("UpdateAvailableTeachingTip")!; + if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo)) + { + var target = this.FindControl("FooterUpdateItem")!; + var tip = this.FindControl("UpdateAvailableTeachingTip")!; - tip.Target = target; - tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}"; - tip.IsOpen = true; - } - }); + tip.Target = target; + tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}"; + tip.IsOpen = true; + } + }); } public void SetDefaultFonts() @@ -284,16 +282,18 @@ public partial class MainWindow : AppWindowBase private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e) { - Dispatcher.UIThread.Post(() => - { - var fileName = Path.GetFileName(e.Url); - var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName; - notificationService.ShowPersistent( - "Failed to load image", - $"Could not load '{displayName}'\n({e.Exception.Message})", - NotificationType.Warning - ); - }); + Dispatcher + .UIThread + .Post(() => + { + var fileName = Path.GetFileName(e.Url); + var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName; + notificationService.ShowPersistent( + "Failed to load image", + $"Could not load '{displayName}'\n({e.Exception.Message})", + NotificationType.Warning + ); + }); } private void TryEnableMicaEffect() @@ -308,11 +308,7 @@ public partial class MainWindow : AppWindowBase if (ActualThemeVariant == ThemeVariant.Dark) { - var color = this.TryFindResource( - "SolidBackgroundFillColorBase", - ThemeVariant.Dark, - out var value - ) + var color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Dark, out var value) ? (Color2)(Color)value! : new Color2(30, 31, 34); @@ -323,11 +319,7 @@ public partial class MainWindow : AppWindowBase else if (ActualThemeVariant == ThemeVariant.Light) { // Similar effect here - var color = this.TryFindResource( - "SolidBackgroundFillColorBase", - ThemeVariant.Light, - out var value - ) + var color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out var value) ? (Color2)(Color)value! : new Color2(243, 243, 243); diff --git a/StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs b/StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs index e9d22fa2..8fc04491 100644 --- a/StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs +++ b/StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs @@ -1,6 +1,6 @@ namespace StabilityMatrix.Core.Models.FileInterfaces; -public class FileSystemPath : IEquatable, IEquatable +public class FileSystemPath : IEquatable, IEquatable, IFormattable { public string FullPath { get; } @@ -8,27 +8,42 @@ public class FileSystemPath : IEquatable, IEquatable { FullPath = path; } - - protected FileSystemPath(FileSystemPath path) : this(path.FullPath) + + protected FileSystemPath(FileSystemPath path) + : this(path.FullPath) { } + + protected FileSystemPath(params string[] paths) + : this(Path.Combine(paths)) { } + + public override string ToString() { + return FullPath; } - protected FileSystemPath(params string[] paths) : this(Path.Combine(paths)) + /// + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { + return ToString(format, formatProvider); } - - public override string ToString() + + /// + /// Overridable IFormattable.ToString method. + /// By default, returns . + /// + protected virtual string ToString(string? format, IFormatProvider? formatProvider) { return FullPath; } public bool Equals(FileSystemPath? other) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; return FullPath == other.FullPath; } - + public bool Equals(string? other) { return string.Equals(FullPath, other); @@ -48,8 +63,9 @@ public class FileSystemPath : IEquatable, IEquatable { return FullPath.GetHashCode(); } - + // Implicit conversions to and from string public static implicit operator string(FileSystemPath path) => path.FullPath; + public static implicit operator FileSystemPath(string path) => new(path); } diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index d653da77..6f3efd8d 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -56,12 +56,14 @@ public class A3WebUI( [SharedFolderType.Karlo] = new[] { "models/karlo" }, [SharedFolderType.TextualInversion] = new[] { "embeddings" }, [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, - [SharedFolderType.ControlNet] = new[] { "models/ControlNet" }, + [SharedFolderType.ControlNet] = new[] { "models/controlnet/ControlNet" }, [SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, [SharedFolderType.LDSR] = new[] { "models/LDSR" }, [SharedFolderType.AfterDetailer] = new[] { "models/adetailer" }, - [SharedFolderType.T2IAdapter] = new[] { "models/controlnet" }, - [SharedFolderType.IpAdapter] = new[] { "models/ipadapter" } + [SharedFolderType.T2IAdapter] = new[] { "models/controlnet/T2IAdapter" }, + [SharedFolderType.IpAdapter] = new[] { "models/controlnet/IpAdapter" }, + [SharedFolderType.InvokeIpAdapters15] = new[] { "models/controlnet/DiffusersIpAdapters" }, + [SharedFolderType.InvokeIpAdaptersXl] = new[] { "models/controlnet/DiffusersIpAdaptersXL" } }; public override Dictionary>? SharedOutputFolders => @@ -183,39 +185,44 @@ public class A3WebUI( ) { progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); - // Setup venv - await using var venvRunner = new PyVenvRunner(Path.Combine(installLocation, "venv")); - venvRunner.WorkingDirectory = installLocation; - await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false); - switch (torchVersion) + var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false); + + await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); + + progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true)); + + var requirements = new FilePath(installLocation, "requirements_versions.txt"); + + var pipArgs = new PipInstallArgs() + .WithTorch("==2.0.1") + .WithTorchVision("==0.15.2") + .WithTorchExtraIndex( + torchVersion switch + { + TorchVersion.Cpu => "cpu", + TorchVersion.Cuda => "cu118", + TorchVersion.Rocm => "rocm5.1.1", + _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null) + } + ) + .WithParsedFromRequirementsTxt( + await requirements.ReadAllTextAsync().ConfigureAwait(false), + excludePattern: "torch" + ); + + if (torchVersion == TorchVersion.Cuda) { - case TorchVersion.Cpu: - await InstallCpuTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); - break; - case TorchVersion.Cuda: - await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); - break; - case TorchVersion.Rocm: - await InstallRocmTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); - break; - default: - throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null); + pipArgs = pipArgs.WithXFormers("==0.0.20"); } + // v1.6.0 needs a httpx qualifier to fix a gradio issue if (versionOptions.VersionTag?.Contains("1.6.0") ?? false) { - await venvRunner.PipInstall("httpx==0.24.1", onConsoleOutput); + pipArgs = pipArgs.AddArg("httpx==0.24.1"); } - // Install requirements file - progress?.Report(new ProgressReport(-1f, "Installing Package Requirements", isIndeterminate: true)); - Logger.Info("Installing requirements_versions.txt"); - - var requirements = new FilePath(installLocation, "requirements_versions.txt"); - await venvRunner - .PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch") - .ConfigureAwait(false); + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true)); @@ -261,21 +268,22 @@ public class A3WebUI( VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit); } - private async Task InstallRocmTorch( - PyVenvRunner venvRunner, - IProgress? progress = null, - Action? onConsoleOutput = null - ) + /// + public override async Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true)); - - await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); + // Migration for `controlnet` -> `controlnet/ControlNet` and `controlnet/T2IAdapter` + // If the original link exists, delete it first + if (installDirectory.JoinDir("models/controlnet") is { IsSymbolicLink: true } controlnetOldLink) + { + Logger.Info("Migration: Removing old controlnet link {Path}", controlnetOldLink); + await controlnetOldLink.DeleteAsync(false).ConfigureAwait(false); + } - await venvRunner - .PipInstall( - new PipInstallArgs().WithTorch("==2.0.1").WithTorchVision().WithTorchExtraIndex("rocm5.1.1"), - onConsoleOutput - ) - .ConfigureAwait(false); + // Resume base setup + await base.SetupModelFolders(installDirectory, sharedFolderMethod).ConfigureAwait(false); } + + /// + public override Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => + SetupModelFolders(installDirectory, sharedFolderMethod); } diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 33888809..1b221a62 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -10,6 +10,7 @@ using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; +using YamlDotNet.Core; using YamlDotNet.RepresentationModel; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -53,12 +54,12 @@ public class ComfyUI( [SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, [SharedFolderType.VAE] = new[] { "models/vae" }, [SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" }, - [SharedFolderType.ControlNet] = new[] { "models/controlnet" }, + [SharedFolderType.ControlNet] = new[] { "models/controlnet/ControlNet" }, [SharedFolderType.GLIGEN] = new[] { "models/gligen" }, [SharedFolderType.ESRGAN] = new[] { "models/upscale_models" }, [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, [SharedFolderType.IpAdapter] = new[] { "models/ipadapter" }, - [SharedFolderType.T2IAdapter] = new[] { "models/controlnet" }, + [SharedFolderType.T2IAdapter] = new[] { "models/controlnet/T2IAdapter" }, }; public override Dictionary>? SharedOutputFolders => @@ -155,80 +156,48 @@ public class ComfyUI( venvRunner.WorkingDirectory = installLocation; await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false); - // Install torch / xformers based on gpu info - switch (torchVersion) + await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); + + progress?.Report(new ProgressReport(-1f, "Installing Package Requirements...", isIndeterminate: true)); + + var pipArgs = new PipInstallArgs(); + + pipArgs = torchVersion switch { - case TorchVersion.Cpu: - await InstallCpuTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); - break; - case TorchVersion.Cuda: - await venvRunner - .PipInstall( - new PipInstallArgs() - .WithTorch("~=2.1.0") - .WithTorchVision() - .WithXFormers("==0.0.22.post4") - .AddArg("--upgrade") - .WithTorchExtraIndex("cu121"), - onConsoleOutput - ) - .ConfigureAwait(false); - break; - case TorchVersion.DirectMl: - await venvRunner - .PipInstall(new PipInstallArgs().WithTorchDirectML(), onConsoleOutput) - .ConfigureAwait(false); - break; - case TorchVersion.Rocm: - await InstallRocmTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); - break; - case TorchVersion.Mps: - await venvRunner - .PipInstall( - new PipInstallArgs() - .AddArg("--pre") - .WithTorch() - .WithTorchVision() - .WithTorchExtraIndex("nightly/cpu"), - onConsoleOutput + TorchVersion.DirectMl => pipArgs.WithTorchDirectML(), + TorchVersion.Mps + => pipArgs.AddArg("--pre").WithTorch().WithTorchVision().WithTorchExtraIndex("nightly/cpu"), + _ + => pipArgs + .AddArg("--upgrade") + .WithTorch("~=2.1.0") + .WithTorchVision() + .WithTorchExtraIndex( + torchVersion switch + { + TorchVersion.Cpu => "cpu", + TorchVersion.Cuda => "cu121", + TorchVersion.Rocm => "rocm5.6", + _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null) + } ) - .ConfigureAwait(false); - break; - default: - throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null); - } + }; - // Install requirements file (skip torch) - progress?.Report(new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true)); + if (torchVersion == TorchVersion.Cuda) + { + pipArgs = pipArgs.WithXFormers("==0.0.22.post4"); + } - var requirementsFile = new FilePath(installLocation, "requirements.txt"); + var requirements = new FilePath(installLocation, "requirements.txt"); - await venvRunner - .PipInstallFromRequirements(requirementsFile, onConsoleOutput, excludes: "torch") - .ConfigureAwait(false); + pipArgs = pipArgs.WithParsedFromRequirementsTxt( + await requirements.ReadAllTextAsync().ConfigureAwait(false), + excludePattern: "torch" + ); - progress?.Report(new ProgressReport(1, "Installing Package Requirements", isIndeterminate: false)); - } + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); - private async Task AutoDetectAndInstallTorch(PyVenvRunner venvRunner, IProgress? progress = null) - { - var gpus = HardwareHelper.IterGpuInfo().ToList(); - if (gpus.Any(g => g.IsNvidia)) - { - await InstallCudaTorch(venvRunner, progress).ConfigureAwait(false); - } - else if (HardwareHelper.PreferRocm()) - { - await InstallRocmTorch(venvRunner, progress).ConfigureAwait(false); - } - else if (HardwareHelper.PreferDirectML()) - { - await InstallDirectMlTorch(venvRunner, progress).ConfigureAwait(false); - } - else - { - await InstallCpuTorch(venvRunner, progress).ConfigureAwait(false); - } + progress?.Report(new ProgressReport(1, "Installed Package Requirements", isIndeterminate: false)); } public override async Task RunPackage( @@ -267,26 +236,55 @@ public class ComfyUI( } } - public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) + public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => + sharedFolderMethod switch + { + SharedFolderMethod.Symlink => SetupModelFoldersSymlink(installDirectory), + SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory), + SharedFolderMethod.None => Task.CompletedTask, + _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) + }; + + public override Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => + SetupModelFolders(installDirectory, sharedFolderMethod); + + public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) + { + return sharedFolderMethod switch + { + SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), + SharedFolderMethod.Configuration => RemoveConfigSection(installDirectory), + SharedFolderMethod.None => Task.CompletedTask, + _ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null) + }; + } + + private async Task SetupModelFoldersSymlink(DirectoryPath installDirectory) { - switch (sharedFolderMethod) + // Migration for `controlnet` -> `controlnet/ControlNet` and `controlnet/T2IAdapter` + // If the original link exists, delete it first + if (installDirectory.JoinDir("models/controlnet") is { IsSymbolicLink: true } controlnetOldLink) { - case SharedFolderMethod.None: - return Task.CompletedTask; - case SharedFolderMethod.Symlink: - return base.SetupModelFolders(installDirectory, sharedFolderMethod); + Logger.Info("Migration: Removing old controlnet link {Path}", controlnetOldLink); + await controlnetOldLink.DeleteAsync(false).ConfigureAwait(false); } - var extraPathsYamlPath = installDirectory + "extra_model_paths.yaml"; + // Resume base setup + await base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink).ConfigureAwait(false); + } + + private async Task SetupModelFoldersConfig(DirectoryPath installDirectory) + { + var extraPathsYamlPath = installDirectory.JoinFile("extra_model_paths.yaml"); var modelsDir = SettingsManager.ModelsDirectory; - var exists = File.Exists(extraPathsYamlPath); - if (!exists) + if (!extraPathsYamlPath.Exists) { Logger.Info("Creating extra_model_paths.yaml"); - File.WriteAllText(extraPathsYamlPath, string.Empty); + extraPathsYamlPath.Create(); } - var yaml = File.ReadAllText(extraPathsYamlPath); + + var yaml = await extraPathsYamlPath.ReadAllTextAsync().ConfigureAwait(false); using var sr = new StringReader(yaml); var yamlStream = new YamlStream(); yamlStream.Load(sr); @@ -307,7 +305,7 @@ public class ComfyUI( if (stabilityMatrixNode.Key != null) { if (stabilityMatrixNode.Value is not YamlMappingNode nodeValue) - return Task.CompletedTask; + return; nodeValue.Children["checkpoints"] = Path.Combine(modelsDir, "StableDiffusion"); nodeValue.Children["vae"] = Path.Combine(modelsDir, "VAE"); @@ -319,7 +317,11 @@ public class ComfyUI( + $"{Path.Combine(modelsDir, "SwinIR")}"; nodeValue.Children["embeddings"] = Path.Combine(modelsDir, "TextualInversion"); nodeValue.Children["hypernetworks"] = Path.Combine(modelsDir, "Hypernetwork"); - nodeValue.Children["controlnet"] = Path.Combine(modelsDir, "ControlNet"); + nodeValue.Children["controlnet"] = string.Join( + '\n', + Path.Combine(modelsDir, "ControlNet"), + Path.Combine(modelsDir, "T2IAdapter") + ); nodeValue.Children["clip"] = Path.Combine(modelsDir, "CLIP"); nodeValue.Children["diffusers"] = Path.Combine(modelsDir, "Diffusers"); nodeValue.Children["gligen"] = Path.Combine(modelsDir, "GLIGEN"); @@ -340,7 +342,10 @@ public class ComfyUI( }, { "embeddings", Path.Combine(modelsDir, "TextualInversion") }, { "hypernetworks", Path.Combine(modelsDir, "Hypernetwork") }, - { "controlnet", Path.Combine(modelsDir, "ControlNet") }, + { + "controlnet", + string.Join('\n', Path.Combine(modelsDir, "ControlNet"), Path.Combine(modelsDir, "T2IAdapter")) + }, { "clip", Path.Combine(modelsDir, "CLIP") }, { "diffusers", Path.Combine(modelsDir, "Diffusers") }, { "gligen", Path.Combine(modelsDir, "GLIGEN") }, @@ -357,82 +362,45 @@ public class ComfyUI( newRootNode.Children.Add(stabilityMatrixNode); - var serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); - var yamlData = serializer.Serialize(newRootNode); - File.WriteAllText(extraPathsYamlPath, yamlData); + var serializer = new SerializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .WithDefaultScalarStyle(ScalarStyle.Literal) + .Build(); - return Task.CompletedTask; + var yamlData = serializer.Serialize(newRootNode); + await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false); } - public override Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => - sharedFolderMethod switch - { - SharedFolderMethod.Symlink => base.UpdateModelFolders(installDirectory, sharedFolderMethod), - SharedFolderMethod.Configuration => SetupModelFolders(installDirectory, sharedFolderMethod), - SharedFolderMethod.None => Task.CompletedTask, - _ => Task.CompletedTask - }; - - public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) + private static async Task RemoveConfigSection(DirectoryPath installDirectory) { - return sharedFolderMethod switch - { - SharedFolderMethod.Configuration => RemoveConfigSection(installDirectory), - SharedFolderMethod.None => Task.CompletedTask, - SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod), - _ => Task.CompletedTask - }; - } + var extraPathsYamlPath = installDirectory.JoinFile("extra_model_paths.yaml"); - private Task RemoveConfigSection(string installDirectory) - { - var extraPathsYamlPath = Path.Combine(installDirectory, "extra_model_paths.yaml"); - var exists = File.Exists(extraPathsYamlPath); - if (!exists) + if (!extraPathsYamlPath.Exists) { - return Task.CompletedTask; + return; } - var yaml = File.ReadAllText(extraPathsYamlPath); + var yaml = await extraPathsYamlPath.ReadAllTextAsync().ConfigureAwait(false); using var sr = new StringReader(yaml); var yamlStream = new YamlStream(); yamlStream.Load(sr); if (!yamlStream.Documents.Any()) { - return Task.CompletedTask; + return; } var root = yamlStream.Documents[0].RootNode; if (root is not YamlMappingNode mappingNode) { - return Task.CompletedTask; + return; } mappingNode.Children.Remove("stability_matrix"); var serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); var yamlData = serializer.Serialize(mappingNode); - File.WriteAllText(extraPathsYamlPath, yamlData); - - return Task.CompletedTask; - } - - private async Task InstallRocmTorch( - PyVenvRunner venvRunner, - IProgress? progress = null, - Action? onConsoleOutput = null - ) - { - progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true)); - - await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); - await venvRunner - .PipInstall( - new PipInstallArgs().WithTorch("==2.0.1").WithTorchVision().WithTorchExtraIndex("rocm5.6"), - onConsoleOutput - ) - .ConfigureAwait(false); + await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false); } } diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs index 33b05647..698c9f1a 100644 --- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.Collections.Immutable; +using System.Diagnostics; using System.Text.RegularExpressions; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; @@ -76,11 +77,11 @@ public class Fooocus( Type = LaunchOptionType.Bool, InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch { - MemoryLevel.Low => "--lowvram", - MemoryLevel.Medium => "--normalvram", + MemoryLevel.Low => "--always-low-vram", + MemoryLevel.Medium => "--always-normal-vram", _ => null }, - Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } + Options = { "--always-high-vram", "--always-normal-vram", "--always-low-vram", "--always-no-vram" } }, new LaunchOptionDefinition { @@ -152,39 +153,38 @@ public class Fooocus( { var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false); - progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true)); + progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true)); + + var pipArgs = new PipInstallArgs(); if (torchVersion == TorchVersion.DirectMl) { - await venvRunner - .PipInstall(new PipInstallArgs().WithTorchDirectML(), onConsoleOutput) - .ConfigureAwait(false); + pipArgs = pipArgs.WithTorchDirectML(); } else { - var extraIndex = torchVersion switch - { - TorchVersion.Cpu => "cpu", - TorchVersion.Cuda => "cu121", - TorchVersion.Rocm => "rocm5.6", - _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null) - }; - - await venvRunner - .PipInstall( - new PipInstallArgs() - .WithTorch("==2.1.0") - .WithTorchVision("==0.16.0") - .WithTorchExtraIndex(extraIndex), - onConsoleOutput - ) - .ConfigureAwait(false); + pipArgs = pipArgs + .WithTorch("==2.1.0") + .WithTorchVision("==0.16.0") + .WithTorchExtraIndex( + torchVersion switch + { + TorchVersion.Cpu => "cpu", + TorchVersion.Cuda => "cu121", + TorchVersion.Rocm => "rocm5.6", + _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null) + } + ); } var requirements = new FilePath(installLocation, "requirements_versions.txt"); - await venvRunner - .PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch") - .ConfigureAwait(false); + + pipArgs = pipArgs.WithParsedFromRequirementsTxt( + await requirements.ReadAllTextAsync().ConfigureAwait(false), + excludePattern: "torch" + ); + + await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false); } public override async Task RunPackage( diff --git a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs index f3e54393..fbf2535f 100644 --- a/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs @@ -1,9 +1,11 @@ using StabilityMatrix.Core.Attributes; 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; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Core.Models.Packages; @@ -26,6 +28,79 @@ public class RuinedFooocus( new("https://raw.githubusercontent.com/runew0lf/pmmconfigs/main/RuinedFooocus_ss.png"); public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert; + public override List LaunchOptions => + new() + { + new LaunchOptionDefinition + { + Name = "Preset", + Type = LaunchOptionType.Bool, + Options = { "--preset anime", "--preset realistic" } + }, + new LaunchOptionDefinition + { + Name = "Port", + Type = LaunchOptionType.String, + Description = "Sets the listen port", + Options = { "--port" } + }, + new LaunchOptionDefinition + { + Name = "Share", + Type = LaunchOptionType.Bool, + Description = "Set whether to share on Gradio", + Options = { "--share" } + }, + new LaunchOptionDefinition + { + Name = "Listen", + Type = LaunchOptionType.String, + Description = "Set the listen interface", + Options = { "--listen" } + }, + new LaunchOptionDefinition + { + Name = "Output Directory", + Type = LaunchOptionType.String, + Description = "Override the output directory", + Options = { "--output-directory" } + }, + new() + { + Name = "VRAM", + Type = LaunchOptionType.Bool, + InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch + { + MemoryLevel.Low => "--lowvram", + MemoryLevel.Medium => "--normalvram", + _ => null + }, + Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } + }, + new LaunchOptionDefinition + { + Name = "Use DirectML", + Type = LaunchOptionType.Bool, + Description = "Use pytorch with DirectML support", + InitialValue = HardwareHelper.PreferDirectML(), + Options = { "--directml" } + }, + new LaunchOptionDefinition + { + Name = "Disable Xformers", + Type = LaunchOptionType.Bool, + InitialValue = !HardwareHelper.HasNvidiaGpu(), + Options = { "--disable-xformers" } + }, + new LaunchOptionDefinition + { + Name = "Auto-Launch", + Type = LaunchOptionType.Bool, + Options = { "--auto-launch" } + }, + LaunchOptionDefinition.Extras + }; + public override async Task InstallPackage( string installLocation, TorchVersion torchVersion, @@ -39,13 +114,23 @@ public class RuinedFooocus( { var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false); - progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true)); - - await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); + progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true)); var requirements = new FilePath(installLocation, "requirements_versions.txt"); + await venvRunner - .PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch") + .PipInstall( + new PipInstallArgs() + .WithTorch("==2.0.1") + .WithTorchVision("==0.15.2") + .WithXFormers("==0.0.20") + .WithTorchExtraIndex("cu118") + .WithParsedFromRequirementsTxt( + await requirements.ReadAllTextAsync().ConfigureAwait(false), + excludePattern: "torch" + ), + onConsoleOutput + ) .ConfigureAwait(false); } else @@ -60,5 +145,9 @@ public class RuinedFooocus( ) .ConfigureAwait(false); } + + // Create output folder since it's not created by default + var outputFolder = new DirectoryPath(installLocation, OutputFolderName); + outputFolder.Create(); } } diff --git a/StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs b/StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs index 00a0e812..1b0dbfa7 100644 --- a/StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs +++ b/StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs @@ -1,6 +1,5 @@ -using System.Diagnostics; +using System.Collections.Immutable; using System.Diagnostics.Contracts; -using OneOf; namespace StabilityMatrix.Core.Processes; @@ -9,14 +8,7 @@ namespace StabilityMatrix.Core.Processes; /// public record ProcessArgsBuilder { - protected ProcessArgsBuilder() { } - - public ProcessArgsBuilder(params Argument[] arguments) - { - Arguments = arguments.ToList(); - } - - public List Arguments { get; init; } = new(); + public IImmutableList Arguments { get; init; } = ImmutableArray.Empty; private IEnumerable ToStringArgs() { @@ -34,6 +26,11 @@ public record ProcessArgsBuilder } } + public ProcessArgsBuilder(params Argument[] arguments) + { + Arguments = arguments.ToImmutableArray(); + } + /// public override string ToString() { @@ -45,8 +42,7 @@ public record ProcessArgsBuilder return ToStringArgs().ToArray(); } - public static implicit operator ProcessArgs(ProcessArgsBuilder builder) => - builder.ToProcessArgs(); + public static implicit operator ProcessArgs(ProcessArgsBuilder builder) => builder.ToProcessArgs(); } public static class ProcessArgBuilderExtensions @@ -55,7 +51,33 @@ public static class ProcessArgBuilderExtensions public static T AddArg(this T builder, Argument argument) where T : ProcessArgsBuilder { - return builder with { Arguments = builder.Arguments.Append(argument).ToList() }; + return builder with { Arguments = builder.Arguments.Add(argument) }; + } + + [Pure] + public static T AddArgs(this T builder, params Argument[] argument) + where T : ProcessArgsBuilder + { + return builder with { Arguments = builder.Arguments.AddRange(argument) }; + } + + [Pure] + public static T UpdateArg(this T builder, string key, Argument argument) + where T : ProcessArgsBuilder + { + var oldArg = builder + .Arguments + .FirstOrDefault(x => x.Match(stringArg => stringArg == key, tupleArg => tupleArg.Item1 == key)); + + if (oldArg is null) + { + return builder.AddArg(argument); + } + + return builder with + { + Arguments = builder.Arguments.Replace(oldArg, argument) + }; } [Pure] @@ -64,15 +86,10 @@ public static class ProcessArgBuilderExtensions { return builder with { - Arguments = builder.Arguments - .Where( - x => - x.Match( - stringArg => stringArg != argumentKey, - tupleArg => tupleArg.Item1 != argumentKey - ) - ) - .ToList() + Arguments = builder + .Arguments + .Where(x => x.Match(stringArg => stringArg != argumentKey, tupleArg => tupleArg.Item1 != argumentKey)) + .ToImmutableArray() }; } } diff --git a/StabilityMatrix.Core/Python/PipInstallArgs.cs b/StabilityMatrix.Core/Python/PipInstallArgs.cs index d16aedf7..68e371f7 100644 --- a/StabilityMatrix.Core/Python/PipInstallArgs.cs +++ b/StabilityMatrix.Core/Python/PipInstallArgs.cs @@ -1,4 +1,7 @@ -using StabilityMatrix.Core.Processes; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using StabilityMatrix.Core.Extensions; +using StabilityMatrix.Core.Processes; namespace StabilityMatrix.Core.Python; @@ -9,20 +12,36 @@ public record PipInstallArgs : ProcessArgsBuilder public PipInstallArgs WithTorch(string version = "") => this.AddArg($"torch{version}"); - public PipInstallArgs WithTorchDirectML(string version = "") => - this.AddArg($"torch-directml{version}"); + public PipInstallArgs WithTorchDirectML(string version = "") => this.AddArg($"torch-directml{version}"); - public PipInstallArgs WithTorchVision(string version = "") => - this.AddArg($"torchvision{version}"); + public PipInstallArgs WithTorchVision(string version = "") => this.AddArg($"torchvision{version}"); public PipInstallArgs WithXFormers(string version = "") => this.AddArg($"xformers{version}"); - public PipInstallArgs WithExtraIndex(string indexUrl) => - this.AddArg(("--extra-index-url", indexUrl)); + public PipInstallArgs WithExtraIndex(string indexUrl) => this.AddArg(("--extra-index-url", indexUrl)); public PipInstallArgs WithTorchExtraIndex(string index) => this.AddArg(("--extra-index-url", $"https://download.pytorch.org/whl/{index}")); + public PipInstallArgs WithParsedFromRequirementsTxt( + string requirements, + [StringSyntax(StringSyntaxAttribute.Regex)] string? excludePattern = null + ) + { + var requirementsEntries = requirements + .SplitLines(StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) + .AsEnumerable(); + + if (excludePattern is not null) + { + var excludeRegex = new Regex($"^{excludePattern}$"); + + requirementsEntries = requirementsEntries.Where(s => !excludeRegex.IsMatch(s)); + } + + return this.AddArgs(requirementsEntries.Select(s => (Argument)s).ToArray()); + } + /// public override string ToString() { diff --git a/StabilityMatrix.Core/Python/PipPackageSpecifier.cs b/StabilityMatrix.Core/Python/PipPackageSpecifier.cs new file mode 100644 index 00000000..5dc58356 --- /dev/null +++ b/StabilityMatrix.Core/Python/PipPackageSpecifier.cs @@ -0,0 +1,87 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using StabilityMatrix.Core.Processes; + +namespace StabilityMatrix.Core.Python; + +public partial record PipPackageSpecifier +{ + public required string Name { get; init; } + + public string? Constraint { get; init; } + + public string? Version { get; init; } + + public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Name; + + public static PipPackageSpecifier Parse(string value) + { + var result = TryParse(value, true, out var packageSpecifier); + + Debug.Assert(result); + + return packageSpecifier!; + } + + public static bool TryParse(string value, [NotNullWhen(true)] out PipPackageSpecifier? packageSpecifier) + { + return TryParse(value, false, out packageSpecifier); + } + + private static bool TryParse( + string value, + bool throwOnFailure, + [NotNullWhen(true)] out PipPackageSpecifier? packageSpecifier + ) + { + var match = PackageSpecifierRegex().Match(value); + if (!match.Success) + { + if (throwOnFailure) + { + throw new ArgumentException($"Invalid package specifier: {value}"); + } + + packageSpecifier = null; + return false; + } + + packageSpecifier = new PipPackageSpecifier + { + Name = match.Groups["package_name"].Value, + Constraint = match.Groups["version_constraint"].Value, + Version = match.Groups["version"].Value + }; + + return true; + } + + /// + public override string ToString() + { + return Name + VersionConstraint; + } + + public static implicit operator Argument(PipPackageSpecifier specifier) + { + return specifier.VersionConstraint is null + ? new Argument(specifier.Name) + : new Argument((specifier.Name, specifier.VersionConstraint)); + } + + public static implicit operator PipPackageSpecifier(string specifier) + { + return Parse(specifier); + } + + /// + /// Regex to match a pip package specifier. + /// + [GeneratedRegex( + "(?[a-zA-Z0-9_]+)(?(?==|>=|<=|>|<|~=|!=)([a-zA-Z0-9_.]+))?", + RegexOptions.CultureInvariant, + 1000 + )] + private static partial Regex PackageSpecifierRegex(); +}