Browse Source

Add settings system info, HardwareInfo reorganize

pull/324/head
Ionite 12 months ago
parent
commit
24f9904a9d
No known key found for this signature in database
  1. 28
      StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs
  2. 36
      StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs
  3. 41
      StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs
  4. 8
      StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs
  5. 9
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  6. 3
      StabilityMatrix.Avalonia/Languages/Resources.resx
  7. 1
      StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs
  8. 42
      StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
  9. 145
      StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
  10. 172
      StabilityMatrix.Core/Helper/HardwareHelper.cs
  11. 7
      StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs
  12. 31
      StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs
  13. 241
      StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs
  14. 10
      StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs
  15. 9
      StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs
  16. 17
      StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs
  17. 55
      StabilityMatrix.Core/Helper/Size.cs
  18. 5
      StabilityMatrix.Core/Models/Packages/A3WebUI.cs
  19. 1
      StabilityMatrix.Core/Models/Packages/BasePackage.cs
  20. 5
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  21. 5
      StabilityMatrix.Core/Models/Packages/Fooocus.cs
  22. 1
      StabilityMatrix.Core/Models/Packages/KohyaSs.cs
  23. 5
      StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs
  24. 5
      StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
  25. 2
      StabilityMatrix.Core/StabilityMatrix.Core.csproj

28
StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs

@ -0,0 +1,28 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
public class CustomStringFormatConverter<T>([StringSyntax("CompositeFormat")] string format)
: IValueConverter
where T : IFormatProvider, new()
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is null ? null : string.Format(new T(), format, value);
}
/// <inheritdoc />
public object? ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture
)
{
return value is null ? null : throw new NotImplementedException();
}
}

36
StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs

@ -0,0 +1,36 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
/// <summary>
/// Converts an index to index + 1
/// </summary>
public class IndexPlusOneConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int i)
{
return i + 1;
}
return value;
}
public object? ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture
)
{
if (value is int i)
{
return i - 1;
}
return value;
}
}

41
StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs

@ -0,0 +1,41 @@
using System;
using Size = StabilityMatrix.Core.Helper.Size;
namespace StabilityMatrix.Avalonia.Converters;
public class MemoryBytesFormatter : ICustomFormatter, IFormatProvider
{
/// <inheritdoc />
public object? GetFormat(Type? formatType)
{
return formatType == typeof(ICustomFormatter) ? this : null;
}
/// <inheritdoc />
public string Format(string? format, object? arg, IFormatProvider? formatProvider)
{
if (format == null || !format.Trim().StartsWith('M'))
{
if (arg is IFormattable formatArg)
{
return formatArg.ToString(format, formatProvider);
}
return arg?.ToString() ?? string.Empty;
}
var value = Convert.ToUInt64(arg);
var result = format.Trim().EndsWith("10", StringComparison.OrdinalIgnoreCase)
? Size.FormatBase10Bytes(value)
: Size.FormatBytes(value);
// Strip i if not Mi
if (!format.Trim().Contains('I', StringComparison.OrdinalIgnoreCase))
{
result = result.Replace("i", string.Empty, StringComparison.OrdinalIgnoreCase);
}
return result;
}
}

8
StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs

@ -1,4 +1,5 @@
using Avalonia.Data.Converters;
using System;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
@ -7,4 +8,9 @@ public static class StringFormatConverters
private static StringFormatValueConverter? _decimalConverter;
public static StringFormatValueConverter Decimal =>
_decimalConverter ??= new StringFormatValueConverter("{0:D}", null);
private static readonly Lazy<IValueConverter> MemoryBytesConverterLazy =
new(() => new CustomStringFormatConverter<MemoryBytesFormatter>("{0:M}"));
public static IValueConverter MemoryBytes => MemoryBytesConverterLazy.Value;
}

9
StabilityMatrix.Avalonia/Languages/Resources.Designer.cs generated

@ -1193,6 +1193,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Information.
/// </summary>
public static string Label_Information {
get {
return ResourceManager.GetString("Label_Information", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Inner exception.
/// </summary>

3
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -843,4 +843,7 @@
<data name="Label_PromptTagsDescription" xml:space="preserve">
<value>Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format)</value>
</data>
<data name="Label_Information" xml:space="preserve">
<value>Information</value>
</data>
</root>

1
StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs

@ -7,6 +7,7 @@ using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
namespace StabilityMatrix.Avalonia.ViewModels;

42
StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs

@ -44,6 +44,7 @@ using StabilityMatrix.Avalonia.Views.Settings;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Python;
@ -116,6 +117,23 @@ public partial class MainSettingsViewModel : PageViewModelBase
[ObservableProperty]
private string? debugGpuInfo;
#region System Info
private static Lazy<IReadOnlyList<GpuInfo>> GpuInfosLazy { get; } =
new(() => HardwareHelper.IterGpuInfo().ToImmutableArray());
public static IReadOnlyList<GpuInfo> GpuInfos => GpuInfosLazy.Value;
[ObservableProperty]
private MemoryInfo memoryInfo;
private readonly DispatcherTimer hardwareInfoUpdateTimer =
new() { Interval = TimeSpan.FromSeconds(2.627) };
public Task<CpuInfo> CpuInfoAsync => HardwareHelper.GetCpuInfoAsync();
#endregion
// Info section
private const int VersionTapCountThreshold = 7;
@ -186,6 +204,25 @@ public partial class MainSettingsViewModel : PageViewModelBase
notificationService,
LogLevel.Warn
);
hardwareInfoUpdateTimer.Tick += OnHardwareInfoUpdateTimerTick;
}
/// <inheritdoc />
public override void OnLoaded()
{
base.OnLoaded();
hardwareInfoUpdateTimer.Start();
OnHardwareInfoUpdateTimerTick(null, null!);
}
/// <inheritdoc />
public override void OnUnloaded()
{
base.OnUnloaded();
hardwareInfoUpdateTimer.Stop();
}
/// <inheritdoc />
@ -199,6 +236,11 @@ public partial class MainSettingsViewModel : PageViewModelBase
accountsService.RefreshAsync().SafeFireAndForget();
}
private void OnHardwareInfoUpdateTimerTick(object? sender, EventArgs e)
{
MemoryInfo = HardwareHelper.GetMemoryInfo();
}
partial void OnSelectedThemeChanged(string? value)
{
// In case design / tests

145
StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml

@ -7,6 +7,8 @@
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
xmlns:hardwareInfo="clr-namespace:StabilityMatrix.Core.Helper.HardwareInfo;assembly=StabilityMatrix.Core"
xmlns:helper="clr-namespace:StabilityMatrix.Core.Helper;assembly=StabilityMatrix.Core"
xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -28,10 +30,17 @@
<controls:UserControlBase.Resources>
<converters:CultureInfoDisplayConverter x:Key="CultureInfoDisplayConverter" />
<converters:IndexPlusOneConverter x:Key="IndexPlusOneConverter" />
<converters:EnumStringConverter x:Key="EnumStringConverter" />
<converters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
</controls:UserControlBase.Resources>
<controls:UserControlBase.Styles>
<Style Selector="sg|SpacedGrid &gt; ui|SettingsExpander">
<Setter Property="Margin" Value="8,0" />
</Style>
</controls:UserControlBase.Styles>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="8,16" Spacing="8">
<!-- Theme -->
@ -104,14 +113,14 @@
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- General -->
<sg:SpacedGrid RowDefinitions="Auto,*" RowSpacing="4">
<TextBlock
Margin="0,0,0,4"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_General}" />
<!-- Link to Inference Sub-Settings -->
<!-- Link to Inference Sub-Settings -->
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0"
@ -201,16 +210,15 @@
</sg:SpacedGrid>
<!-- System Options -->
<sg:SpacedGrid RowDefinitions="Auto,Auto,Auto,Auto" RowSpacing="4">
<sg:SpacedGrid RowDefinitions="Auto,Auto,Auto,Auto,Auto" RowSpacing="4">
<TextBlock
Margin="0,0,0,4"
FontWeight="Medium"
Text="{x:Static lang:Resources.Label_System}" />
<!-- Updates page -->
<!-- Updates page -->
<ui:SettingsExpander
Grid.Row="1"
Margin="8,0"
ActionIconSource="ChevronRight"
Command="{Binding NavigateToSubPageCommand}"
CommandParameter="{x:Type vmSettings:UpdateSettingsViewModel}"
@ -226,7 +234,6 @@
<ui:SettingsExpander
Grid.Row="2"
Margin="8,0"
Description="{x:Static lang:Resources.Label_AddToStartMenu_Details}"
Header="{x:Static lang:Resources.Label_AddToStartMenu}"
IconSource="StarAdd"
@ -266,7 +273,6 @@
<ui:SettingsExpander
Grid.Row="3"
Margin="8,0"
Description="{x:Static lang:Resources.Label_SelectNewDataDirectory_Details}"
IconSource="MoveToFolder">
<ui:SettingsExpander.Header>
@ -294,6 +300,131 @@
</Button>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander Grid.Row="4" Header="{x:Static lang:Resources.Label_Information}">
<ui:SettingsExpander.IconSource>
<fluentIcons:SymbolIconSource
FontSize="10"
IsFilled="True"
Symbol="Info" />
</ui:SettingsExpander.IconSource>
<!-- Cpu -->
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.IconSource>
<controls:FASymbolIconSource
FontSize="10"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Symbol="fa-solid fa-microchip" />
</ui:SettingsExpanderItem.IconSource>
<sg:SpacedGrid
DataContext="{Binding CpuInfoAsync^}"
ColumnDefinitions="Auto,Auto"
ColumnSpacing="16"
RowDefinitions="Auto,Auto">
<TextBlock Grid.Column="0" Text="CPU" />
<SelectableTextBlock
Grid.Row="0"
Grid.Column="1"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Text="{Binding ProcessorCaption}"
TextWrapping="WrapWithOverflow" />
<!--<TextBlock
Grid.Row="1"
Grid.Column="0" Text="Available Memory" />
<SelectableTextBlock
Grid.Row="1"
Grid.Column="1"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Text="{Binding MemoryInfo.AvailablePhysicalBytes, Converter={x:Static converters:StringFormatConverters.MemoryBytes}}"
TextWrapping="WrapWithOverflow" />-->
</sg:SpacedGrid>
</ui:SettingsExpanderItem>
<!-- Memory -->
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.IconSource>
<controls:FASymbolIconSource
FontSize="10"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Symbol="fa-solid fa-memory" />
</ui:SettingsExpanderItem.IconSource>
<sg:SpacedGrid
DataContext="{Binding MemoryInfo}"
ColumnDefinitions="Auto,Auto"
ColumnSpacing="16"
RowDefinitions="Auto,Auto">
<TextBlock Grid.Column="0" Text="Total Memory" />
<SelectableTextBlock
Grid.Row="0"
Grid.Column="1"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
TextWrapping="WrapWithOverflow">
<SelectableTextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1} usable)">
<Binding Path="TotalInstalledBytes" Converter="{x:Static converters:StringFormatConverters.MemoryBytes}"/>
<Binding Path="TotalPhysicalBytes" Converter="{x:Static converters:StringFormatConverters.MemoryBytes}"/>
</MultiBinding>
</SelectableTextBlock.Text>
</SelectableTextBlock>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="Available Memory" />
<SelectableTextBlock
Grid.Row="1"
Grid.Column="1"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Text="{Binding AvailablePhysicalBytes, Converter={x:Static converters:StringFormatConverters.MemoryBytes}}"
TextWrapping="WrapWithOverflow" />
</sg:SpacedGrid>
</ui:SettingsExpanderItem>
<!-- GPUs -->
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.IconSource>
<controls:FASymbolIconSource
FontSize="10"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Symbol="fa-solid fa-tachograph-digital" />
</ui:SettingsExpanderItem.IconSource>
<ItemsControl ItemsSource="{Binding GpuInfos}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="hardwareInfo:GpuInfo">
<sg:SpacedGrid ColumnDefinitions="Auto,Auto,Auto" RowDefinitions="Auto,Auto">
<TextBlock Text="{Binding Index, StringFormat={}{0}, Converter={StaticResource IndexPlusOneConverter}}" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<SelectableTextBlock
Grid.Row="0"
Grid.Column="1"
Text="{Binding Name}" />
<SelectableTextBlock
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
IsVisible="{Binding !!MemoryBytes}"
Text="{Binding MemoryBytes, Converter={x:Static converters:StringFormatConverters.MemoryBytes}}"
TextWrapping="WrapWithOverflow" />
</sg:SpacedGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</sg:SpacedGrid>
<!-- Debug Options -->

172
StabilityMatrix.Core/Helper/HardwareHelper.cs

@ -1,172 +0,0 @@
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace StabilityMatrix.Core.Helper;
public static partial class HardwareHelper
{
private static IReadOnlyList<GpuInfo>? cachedGpuInfos;
private static string RunBashCommand(string command)
{
var processInfo = new ProcessStartInfo("bash", "-c \"" + command + "\"")
{
UseShellExecute = false,
RedirectStandardOutput = true
};
var process = Process.Start(processInfo);
process.WaitForExit();
var output = process.StandardOutput.ReadToEnd();
return output;
}
[SupportedOSPlatform("windows")]
private static IEnumerable<GpuInfo> IterGpuInfoWindows()
{
const string gpuRegistryKeyPath =
@"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}";
using var baseKey = Registry.LocalMachine.OpenSubKey(gpuRegistryKeyPath);
if (baseKey == null) yield break;
foreach (var subKeyName in baseKey.GetSubKeyNames().Where(k => k.StartsWith("0")))
{
using var subKey = baseKey.OpenSubKey(subKeyName);
if (subKey != null)
{
yield return new GpuInfo
{
Name = subKey.GetValue("DriverDesc")?.ToString(),
MemoryBytes = Convert.ToUInt64(subKey.GetValue("HardwareInformation.qwMemorySize")),
};
}
}
}
[SupportedOSPlatform("linux")]
private static IEnumerable<GpuInfo> IterGpuInfoLinux()
{
var output = RunBashCommand("lspci | grep VGA");
var gpuLines = output.Split("\n");
foreach (var line in gpuLines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
var gpuId = line.Split(' ')[0]; // The GPU ID is the first part of the line
var gpuOutput = RunBashCommand($"lspci -v -s {gpuId}");
ulong memoryBytes = 0;
string? name = null;
// Parse output with regex
var match = Regex.Match(gpuOutput, @"VGA compatible controller: ([^\n]*)");
if (match.Success)
{
name = match.Groups[1].Value.Trim();
}
match = Regex.Match(gpuOutput, @"prefetchable\) \[size=(\\d+)M\]");
if (match.Success)
{
memoryBytes = ulong.Parse(match.Groups[1].Value) * 1024 * 1024;
}
yield return new GpuInfo { Name = name, MemoryBytes = memoryBytes };
}
}
/// <summary>
/// Yields GpuInfo for each GPU in the system.
/// </summary>
public static IEnumerable<GpuInfo> IterGpuInfo()
{
if (Compat.IsWindows)
{
return IterGpuInfoWindows();
}
else if (Compat.IsLinux)
{
// Since this requires shell commands, fetch cached value if available.
if (cachedGpuInfos is not null)
{
return cachedGpuInfos;
}
// No cache, fetch and cache.
cachedGpuInfos = IterGpuInfoLinux().ToList();
return cachedGpuInfos;
}
// TODO: Implement for macOS
return Enumerable.Empty<GpuInfo>();
}
/// <summary>
/// Return true if the system has at least one Nvidia GPU.
/// </summary>
public static bool HasNvidiaGpu()
{
return IterGpuInfo().Any(gpu => gpu.IsNvidia);
}
/// <summary>
/// Return true if the system has at least one AMD GPU.
/// </summary>
public static bool HasAmdGpu()
{
return IterGpuInfo().Any(gpu => gpu.IsAmd);
}
// Set ROCm for default if AMD and Linux
public static bool PreferRocm() => !HasNvidiaGpu()
&& HasAmdGpu()
&& Compat.IsLinux;
// Set DirectML for default if AMD and Windows
public static bool PreferDirectML() => !HasNvidiaGpu()
&& HasAmdGpu()
&& Compat.IsWindows;
}
public enum Level
{
Unknown,
Low,
Medium,
High
}
public record GpuInfo
{
public string? Name { get; init; } = string.Empty;
public ulong MemoryBytes { get; init; }
public Level? MemoryLevel => MemoryBytes switch
{
<= 0 => Level.Unknown,
< 4 * Size.GiB => Level.Low,
< 8 * Size.GiB => Level.Medium,
_ => Level.High
};
public bool IsNvidia
{
get
{
var name = Name?.ToLowerInvariant();
if (string.IsNullOrEmpty(name)) return false;
return name.Contains("nvidia")
|| name.Contains("tesla");
}
}
public bool IsAmd => Name?.ToLowerInvariant().Contains("amd") ?? false;
}

7
StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs

@ -0,0 +1,7 @@
namespace StabilityMatrix.Core.Helper.HardwareInfo;
public readonly record struct CpuInfo
{
public string ProcessorCaption { get; init; }
public string ProcessorName { get; init; }
}

31
StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs

@ -0,0 +1,31 @@
namespace StabilityMatrix.Core.Helper.HardwareInfo;
public record GpuInfo
{
public int Index { get; init; }
public string? Name { get; init; } = string.Empty;
public ulong MemoryBytes { get; init; }
public MemoryLevel? MemoryLevel =>
MemoryBytes switch
{
<= 0 => HardwareInfo.MemoryLevel.Unknown,
< 4 * Size.GiB => HardwareInfo.MemoryLevel.Low,
< 8 * Size.GiB => HardwareInfo.MemoryLevel.Medium,
_ => HardwareInfo.MemoryLevel.High
};
public bool IsNvidia
{
get
{
var name = Name?.ToLowerInvariant();
if (string.IsNullOrEmpty(name))
return false;
return name.Contains("nvidia") || name.Contains("tesla");
}
}
public bool IsAmd => Name?.Contains("amd", StringComparison.OrdinalIgnoreCase) ?? false;
}

241
StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs

@ -0,0 +1,241 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using Hardware.Info;
using Microsoft.Win32;
namespace StabilityMatrix.Core.Helper.HardwareInfo;
public static partial class HardwareHelper
{
private static IReadOnlyList<GpuInfo>? cachedGpuInfos;
private static readonly Lazy<IHardwareInfo> HardwareInfoLazy =
new(() => new Hardware.Info.HardwareInfo());
public static IHardwareInfo HardwareInfo => HardwareInfoLazy.Value;
private static string RunBashCommand(string command)
{
var processInfo = new ProcessStartInfo("bash", "-c \"" + command + "\"")
{
UseShellExecute = false,
RedirectStandardOutput = true
};
var process = Process.Start(processInfo);
process.WaitForExit();
var output = process.StandardOutput.ReadToEnd();
return output;
}
[SupportedOSPlatform("windows")]
private static IEnumerable<GpuInfo> IterGpuInfoWindows()
{
const string gpuRegistryKeyPath =
@"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}";
using var baseKey = Registry.LocalMachine.OpenSubKey(gpuRegistryKeyPath);
if (baseKey == null)
yield break;
var gpuIndex = 0;
foreach (var subKeyName in baseKey.GetSubKeyNames().Where(k => k.StartsWith("0")))
{
using var subKey = baseKey.OpenSubKey(subKeyName);
if (subKey != null)
{
yield return new GpuInfo
{
Index = gpuIndex++,
Name = subKey.GetValue("DriverDesc")?.ToString(),
MemoryBytes = Convert.ToUInt64(
subKey.GetValue("HardwareInformation.qwMemorySize")
),
};
}
}
}
[SupportedOSPlatform("linux")]
private static IEnumerable<GpuInfo> IterGpuInfoLinux()
{
var output = RunBashCommand("lspci | grep VGA");
var gpuLines = output.Split("\n");
var gpuIndex = 0;
foreach (var line in gpuLines)
{
if (string.IsNullOrWhiteSpace(line))
continue;
var gpuId = line.Split(' ')[0]; // The GPU ID is the first part of the line
var gpuOutput = RunBashCommand($"lspci -v -s {gpuId}");
ulong memoryBytes = 0;
string? name = null;
// Parse output with regex
var match = Regex.Match(gpuOutput, @"VGA compatible controller: ([^\n]*)");
if (match.Success)
{
name = match.Groups[1].Value.Trim();
}
match = Regex.Match(gpuOutput, @"prefetchable\) \[size=(\\d+)M\]");
if (match.Success)
{
memoryBytes = ulong.Parse(match.Groups[1].Value) * 1024 * 1024;
}
yield return new GpuInfo
{
Index = gpuIndex++,
Name = name,
MemoryBytes = memoryBytes
};
}
}
/// <summary>
/// Yields GpuInfo for each GPU in the system.
/// </summary>
public static IEnumerable<GpuInfo> IterGpuInfo()
{
if (Compat.IsWindows)
{
return IterGpuInfoWindows();
}
else if (Compat.IsLinux)
{
// Since this requires shell commands, fetch cached value if available.
if (cachedGpuInfos is not null)
{
return cachedGpuInfos;
}
// No cache, fetch and cache.
cachedGpuInfos = IterGpuInfoLinux().ToList();
return cachedGpuInfos;
}
// TODO: Implement for macOS
return Enumerable.Empty<GpuInfo>();
}
/// <summary>
/// Return true if the system has at least one Nvidia GPU.
/// </summary>
public static bool HasNvidiaGpu()
{
return IterGpuInfo().Any(gpu => gpu.IsNvidia);
}
/// <summary>
/// Return true if the system has at least one AMD GPU.
/// </summary>
public static bool HasAmdGpu()
{
return IterGpuInfo().Any(gpu => gpu.IsAmd);
}
// Set ROCm for default if AMD and Linux
public static bool PreferRocm() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsLinux;
// Set DirectML for default if AMD and Windows
public static bool PreferDirectML() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsWindows;
/// <summary>
/// Gets the total and available physical memory in bytes.
/// </summary>
public static MemoryInfo GetMemoryInfo() =>
Compat.IsWindows ? GetMemoryInfoImplWindows() : GetMemoryInfoImplGeneric();
[SupportedOSPlatform("windows")]
private static MemoryInfo GetMemoryInfoImplWindows()
{
var memoryStatus = new Win32MemoryStatusEx();
if (!GlobalMemoryStatusEx(ref memoryStatus))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (!GetPhysicallyInstalledSystemMemory(out var installedMemoryKb))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return new MemoryInfo
{
TotalInstalledBytes = (ulong)installedMemoryKb * 1024,
TotalPhysicalBytes = memoryStatus.UllTotalPhys,
AvailablePhysicalBytes = memoryStatus.UllAvailPhys
};
}
private static MemoryInfo GetMemoryInfoImplGeneric()
{
HardwareInfo.RefreshMemoryList();
return new MemoryInfo
{
TotalPhysicalBytes = HardwareInfo.MemoryStatus.TotalPhysical,
AvailablePhysicalBytes = HardwareInfo.MemoryStatus.AvailablePhysical
};
}
/// <summary>
/// Gets cpu info
/// </summary>
public static Task<CpuInfo> GetCpuInfoAsync() =>
Compat.IsWindows ? Task.FromResult(GetCpuInfoImplWindows()) : GetCpuInfoImplGenericAsync();
[SupportedOSPlatform("windows")]
private static CpuInfo GetCpuInfoImplWindows()
{
var info = new CpuInfo();
using var processorKey = Registry.LocalMachine.OpenSubKey(
@"Hardware\Description\System\CentralProcessor\0",
RegistryKeyPermissionCheck.ReadSubTree
);
if (processorKey?.GetValue("ProcessorNameString") is string processorName)
{
info = info with { ProcessorCaption = processorName.Trim() };
}
return info;
}
private static Task<CpuInfo> GetCpuInfoImplGenericAsync()
{
return Task.Run(() =>
{
HardwareInfo.RefreshCPUList();
return new CpuInfo
{
ProcessorCaption = HardwareInfo.CpuList.FirstOrDefault()?.Caption.Trim() ?? ""
};
});
}
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GetPhysicallyInstalledSystemMemory(out long totalMemoryInKilobytes);
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GlobalMemoryStatusEx(ref Win32MemoryStatusEx lpBuffer);
}

10
StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs

@ -0,0 +1,10 @@
namespace StabilityMatrix.Core.Helper.HardwareInfo;
public readonly record struct MemoryInfo
{
public ulong TotalInstalledBytes { get; init; }
public ulong TotalPhysicalBytes { get; init; }
public ulong AvailablePhysicalBytes { get; init; }
}

9
StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs

@ -0,0 +1,9 @@
namespace StabilityMatrix.Core.Helper.HardwareInfo;
public enum MemoryLevel
{
Unknown,
Low,
Medium,
High
}

17
StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs

@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace StabilityMatrix.Core.Helper.HardwareInfo;
[StructLayout(LayoutKind.Sequential)]
public struct Win32MemoryStatusEx
{
public uint DwLength = (uint)Marshal.SizeOf(typeof(Win32MemoryStatusEx));
public uint DwMemoryLoad = 0;
public ulong UllTotalPhys = 0;
public ulong UllAvailPhys = 0;
public ulong UllTotalPageFile = 0;
public ulong UllAvailPageFile = 0;
public ulong UllTotalVirtual = 0;
public ulong UllAvailVirtual = 0;
public ulong UllAvailExtendedVirtual = 0;
}

55
StabilityMatrix.Core/Helper/Size.cs

@ -9,25 +9,60 @@ public static class Size
public const ulong MiB = KiB * 1024;
public const ulong GiB = MiB * 1024;
public static string FormatBytes(ulong bytes)
private static string TrimZero(string value)
{
return value.TrimEnd('0').TrimEnd('.');
}
public static string FormatBytes(ulong bytes, bool trimZero = false)
{
return bytes switch
{
< KiB => $"{bytes} B",
< MiB => $"{bytes / (double)KiB:0.0} KiB",
< GiB => $"{bytes / (double)MiB:0.0} MiB",
_ => $"{bytes / (double)GiB:0.0} GiB"
< KiB => $"{bytes:0} Bytes",
< MiB
=> (
trimZero
? $"{bytes / (double)KiB:0.0}".TrimEnd('0').TrimEnd('.')
: $"{bytes / (double)KiB:0.0}"
) + " KiB",
< GiB
=> (
trimZero
? $"{bytes / (double)MiB:0.0}".TrimEnd('0').TrimEnd('.')
: $"{bytes / (double)MiB:0.0}"
) + " MiB",
_
=> (
trimZero
? $"{bytes / (double)GiB:0.0}".TrimEnd('0').TrimEnd('.')
: $"{bytes / (double)GiB:0.0}"
) + " GiB"
};
}
public static string FormatBase10Bytes(ulong bytes)
public static string FormatBase10Bytes(ulong bytes, bool trimZero = false)
{
return bytes switch
{
< KiB => $"{bytes} Bytes",
< MiB => $"{bytes / (double)KiB:0.0} KB",
< GiB => $"{bytes / (double)MiB:0.0} MB",
_ => $"{bytes / (double)GiB:0.00} GB"
< KiB => $"{bytes:0} Bytes",
< MiB
=> (
trimZero
? $"{bytes / (double)KiB:0.0}".TrimEnd('0').TrimEnd('.')
: $"{bytes / (double)KiB:0.0}"
) + " KB",
< GiB
=> (
trimZero
? $"{bytes / (double)MiB:0.0}".TrimEnd('0').TrimEnd('.')
: $"{bytes / (double)MiB:0.0}"
) + " MB",
_
=> (
trimZero
? $"{bytes / (double)GiB:0.00}".TrimEnd('0').TrimEnd('.')
: $"{bytes / (double)GiB:0.00}"
) + " GB"
};
}

5
StabilityMatrix.Core/Models/Packages/A3WebUI.cs

@ -5,6 +5,7 @@ using NLog;
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;
@ -103,8 +104,8 @@ public class A3WebUI : BaseGitPackage
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--medvram",
_ => null
},
Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" }

1
StabilityMatrix.Core/Models/Packages/BasePackage.cs

@ -1,5 +1,6 @@
using Octokit;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;

5
StabilityMatrix.Core/Models/Packages/ComfyUI.cs

@ -4,6 +4,7 @@ using NLog;
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;
@ -92,8 +93,8 @@ public class ComfyUI : BaseGitPackage
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--normalvram",
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--normalvram",
_ => null
},
Options = { "--highvram", "--normalvram", "--lowvram", "--novram" }

5
StabilityMatrix.Core/Models/Packages/Fooocus.cs

@ -3,6 +3,7 @@ using System.Text.RegularExpressions;
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;
@ -84,8 +85,8 @@ public class Fooocus : BaseGitPackage
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--normalvram",
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--normalvram",
_ => null
},
Options = { "--highvram", "--normalvram", "--lowvram", "--novram" }

1
StabilityMatrix.Core/Models/Packages/KohyaSs.cs

@ -4,6 +4,7 @@ using StabilityMatrix.Core.Attributes;
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;

5
StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs

@ -5,6 +5,7 @@ using NLog;
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;
@ -104,8 +105,8 @@ public class StableDiffusionUx : BaseGitPackage
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--medvram",
_ => null
},
Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" }

5
StabilityMatrix.Core/Models/Packages/VladAutomatic.cs

@ -7,6 +7,7 @@ using NLog;
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;
@ -110,8 +111,8 @@ public class VladAutomatic : BaseGitPackage
.Select(gpu => gpu.MemoryLevel)
.Max() switch
{
Level.Low => "--lowvram",
Level.Medium => "--medvram",
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--medvram",
_ => null
},
Options = new() { "--lowvram", "--medvram" }

2
StabilityMatrix.Core/StabilityMatrix.Core.csproj

@ -7,6 +7,7 @@
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@ -24,6 +25,7 @@
<PackageReference Include="DeviceId.Windows" Version="6.5.0" />
<PackageReference Include="DeviceId.Windows.Wmi" Version="6.5.1" />
<PackageReference Include="DynamicData" Version="8.1.1" />
<PackageReference Include="Hardware.Info" Version="100.0.0.1" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="LiteDB.Async" Version="0.1.7" />
<PackageReference Include="MetadataExtractor" Version="2.8.1" />

Loading…
Cancel
Save