diff --git a/Apps/Amuse.App/Backend/StableDiffusionCppClient.cs b/Apps/Amuse.App/Backend/StableDiffusionCppClient.cs new file mode 100644 index 0000000..8005fbc --- /dev/null +++ b/Apps/Amuse.App/Backend/StableDiffusionCppClient.cs @@ -0,0 +1,45 @@ +using Amuse.App.Services; +using Amuse.Common; +using Amuse.Common.Config; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; + +namespace Amuse.App.Runtime +{ + /// + /// PipelineClient implemntation for Amuse.Host.StableDiffusionCpp + /// Implements the + /// + /// + public sealed class StableDiffusionCppClient : BackendClient + { + /// + /// Initializes a new instance of the class. + /// + /// The settings. + /// The media service. + /// The logger. + public StableDiffusionCppClient(Settings settings, IMediaService mediaService, ILogger logger) + : base(settings, mediaService, logger) { } + + + /// + /// Create PipelineClient targeting Amuse.Host.StableDiffusionCpp. + /// + /// The cancellation token. + /// + protected override async Task CreatePipelineClientAsync(CancellationToken cancellationToken = default) + { + var createOptions = new PipelineCreateOptions(); + var clientConfig = new ClientConfig + { + ServerPath = App.DirectoryServer, + ServerType = ServerType.StableDiffusionCpp, + IsDebugMode = Settings.IsServerDebugEnabled, + }; + return await CreatePipelineClientAsync(clientConfig, createOptions, cancellationToken); + } + + } +} diff --git a/Apps/Amuse.App/Services/GenerateService.cs b/Apps/Amuse.App/Services/GenerateService.cs index c9eb7d6..4e67c62 100644 --- a/Apps/Amuse.App/Services/GenerateService.cs +++ b/Apps/Amuse.App/Services/GenerateService.cs @@ -112,6 +112,7 @@ public async Task LoadAsync(PipelineModel pipeline, IProgress { BackendType.PyTorch => new PyTorchBackendClient(_settings, _mediaService, _environmentService, _logger), BackendType.OnnxRuntime => new OnnxBackendClient(_settings, _mediaService, _logger), + BackendType.StableDiffusionCpp => new StableDiffusionCppClient(_settings, _mediaService, _logger), _ => throw new NotImplementedException() }; diff --git a/Apps/Amuse.Common/Config/ServerConfig.cs b/Apps/Amuse.Common/Config/ServerConfig.cs index 8f72d15..f37cc4a 100644 --- a/Apps/Amuse.Common/Config/ServerConfig.cs +++ b/Apps/Amuse.Common/Config/ServerConfig.cs @@ -45,6 +45,17 @@ public static ServerConfig GetConfig(ServerType serverType, string directoryBase ChannelPipeName = "AmusePyTorch.PipeName", ChannelProgress = "AmusePyTorch.Progress" } + }, + { + ServerType.StableDiffusionCpp, new ServerConfig + { + Name = "AmuseStableDiffusionCpp", + Arguments = [nameof(ServerType.StableDiffusionCpp)], + Executable = "AmuseHost.StableDiffusionCpp.exe", + ChannelCommand = "AmuseStableDiffusionCpp.Command", + ChannelPipeName = "AmuseStableDiffusionCpp.PipeName", + ChannelProgress = "AmuseStableDiffusionCpp.Progress" + } } }; } diff --git a/Apps/Amuse.Common/Enums.cs b/Apps/Amuse.Common/Enums.cs index 12f767f..e5bfb16 100644 --- a/Apps/Amuse.Common/Enums.cs +++ b/Apps/Amuse.Common/Enums.cs @@ -5,7 +5,8 @@ namespace Amuse.Common public enum ServerType { OnnxRuntime = 0, - PyTorch = 10 + PyTorch = 10, + StableDiffusionCpp = 20 } public enum ProcessType diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Amuse.Host.StableDiffusionCpp.csproj b/Apps/Amuse.Host.StableDiffusionCpp/Amuse.Host.StableDiffusionCpp.csproj new file mode 100644 index 0000000..4210c4c --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Amuse.Host.StableDiffusionCpp.csproj @@ -0,0 +1,45 @@ + + + + Exe + AmuseHost.StableDiffusionCpp + net10.0-windows10.0.17763.0 + x64 + Icon.ico + Debug;Release;Release_Installer + false + ..\Amuse.App\bin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/BackendType.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/BackendType.cs new file mode 100644 index 0000000..84b6b3e --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/BackendType.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public enum BackendType + { + [Display(Name = "", ShortName = "cpu")] + CPU = 0, + + [Display(Name = "", ShortName = "cuda")] + CUDA = 1, + + [Display(Name = "", ShortName = "vulkan")] + Vulkan = 2, + + [Display(Name = "", ShortName = "metal")] + Metal = 3, + + [Display(Name = "", ShortName = "rocm")] + ROCM = 4 + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/CapabilitiesModel.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/CapabilitiesModel.cs new file mode 100644 index 0000000..a21f31c --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/CapabilitiesModel.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record CapabilitiesModel + { + [JsonPropertyName("samplers")] + public string[] Samplers { get; set; } + + + [JsonPropertyName("schedulers")] + public string[] Schedulers { get; set; } + + + [JsonPropertyName("defaults_by_mode")] + public DefaultParams DefaultParams { get; set; } + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/DefaultParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/DefaultParams.cs new file mode 100644 index 0000000..fcd44e8 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/DefaultParams.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record DefaultParams + { + [JsonPropertyName("img_gen")] + public ImageParams ImageParams { get; set; } + + [JsonPropertyName("vid_gen")] + public VideoParams VideoParams { get; set; } + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/GuidanceParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/GuidanceParams.cs new file mode 100644 index 0000000..c16f03b --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/GuidanceParams.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record GuidanceParams + { + [JsonPropertyName("txt_cfg")] + public float? TxtCfg { get; set; } + + [JsonPropertyName("img_cfg")] + public float? ImgCfg { get; set; } + + [JsonPropertyName("distilled_guidance")] + public float? DistilledGuidance { get; set; } + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/ImageParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/ImageParams.cs new file mode 100644 index 0000000..3bc6976 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/ImageParams.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record ImageParams + { + [JsonPropertyName("prompt")] + public string Prompt { get; set; } + + [JsonPropertyName("negative_prompt")] + public string NegativePrompt { get; set; } + + [JsonPropertyName("width")] + public int Width { get; set; } + + [JsonPropertyName("height")] + public int Height { get; set; } + + [JsonPropertyName("seed")] + public int Seed { get; set; } + + [JsonPropertyName("strength")] + public double Strength { get; set; } + + [JsonPropertyName("batch_count")] + public int BatchCount { get;} = 1; + + [JsonPropertyName("clip_skip")] + public int ClipSkip { get; set; } = -1; + + [JsonPropertyName("control_strength")] + public double ControlStrength { get; set; } + + [JsonPropertyName("embed_image_metadata")] + public bool EmbedImageMetadata { get; set; } + + [JsonPropertyName("init_image")] + public string InitImage { get; set; } + + [JsonPropertyName("ref_images")] + public List RefImages { get; set; } = []; + + [JsonPropertyName("mask_image")] + public string MaskImage { get; set; } + + [JsonPropertyName("control_image")] + public string ControlImage { get; set; } + + [JsonPropertyName("sample_params")] + public SampleParams SampleParams { get; set; } + + [JsonPropertyName("lora")] + public List Lora { get; set; } = []; + + [JsonPropertyName("vae_tiling_params")] + public VaeTilingParams VaeTilingParams { get; set; } + + [JsonPropertyName("output_format")] + public string OutputFormat { get; set; } = "png"; + + [JsonPropertyName("output_compression")] + public int OutputCompression { get; set; } = 100; + + [JsonPropertyName("auto_resize_ref_image")] + public bool AutoResizeRefImage { get; set; } + + [JsonPropertyName("increase_ref_index")] + public bool IncreaseRefIndex { get; set; } + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/ImageResult.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/ImageResult.cs new file mode 100644 index 0000000..357919a --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/ImageResult.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public sealed class ImageResult + { + [JsonPropertyName("index")] + public int Index { get; set; } + + [JsonPropertyName("b64_json")] + public string B64Json { get; set; } + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/JobModel.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/JobModel.cs new file mode 100644 index 0000000..1a460b4 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/JobModel.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public sealed class JobModel + { + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("status")] + public JobStatus Status { get; set; } + + [JsonPropertyName("created")] + public long Created { get; set; } + + [JsonPropertyName("started")] + public long Started { get; set; } + + [JsonPropertyName("completed")] + public long? Completed { get; set; } + + [JsonPropertyName("queue_position")] + public int QueuePosition { get; set; } + + [JsonPropertyName("result")] + public JobResult Result { get; set; } + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/JobResult.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/JobResult.cs new file mode 100644 index 0000000..db7e4ca --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/JobResult.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public sealed class JobResult + { + [JsonPropertyName("output_format")] + public string OutputFormat { get; set; } + + [JsonPropertyName("mime_type")] + public string MimeType { get; set; } + + [JsonPropertyName("fps")] + public int FrameRate { get; set; } + + [JsonPropertyName("frame_count")] + public int FrameCount { get; set; } + + [JsonPropertyName("images")] + public List Images { get; set; } = []; + + [JsonPropertyName("b64_json")] + public string Video { get; set; } + + public byte[] GetImageBytes(int index = 0) + { + var image = Images.ElementAtOrDefault(index); + if (image == null) + return null; + + return Convert.FromBase64String(image.B64Json); + } + + + public byte[] GetVideoBytes() + { + if (string.IsNullOrEmpty(Video)) + return null; + + return Convert.FromBase64String(Video); + } + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/JobStatus.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/JobStatus.cs new file mode 100644 index 0000000..03a7fc9 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/JobStatus.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public enum JobStatus + { + [JsonStringEnumMemberName("queued")] + Queued = 0, + + [JsonStringEnumMemberName("generating")] + Generating = 1, + + [JsonStringEnumMemberName("completed")] + Completed = 2, + + [JsonStringEnumMemberName("failed")] + Failed = 3, + + [JsonStringEnumMemberName("cancelled")] + Cancelled = 4 + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/LoraParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/LoraParams.cs new file mode 100644 index 0000000..eb3eb2d --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/LoraParams.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record LoraParams + { + [JsonPropertyName("path")] + public string Path { get; set; } = ""; + + [JsonPropertyName("multiplier")] + public float Multiplier { get; set; } = 1.0f; + + [JsonPropertyName("is_high_noise")] + public bool IsHighNoise { get; set; } + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/SampleParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/SampleParams.cs new file mode 100644 index 0000000..2dcf013 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/SampleParams.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record SampleParams + { + [JsonPropertyName("scheduler")] + public string Scheduler { get; set; } + + [JsonPropertyName("sample_method")] + public string SampleMethod { get; set; } + + [JsonPropertyName("sample_steps")] + public int SampleSteps { get; set; } + + [JsonPropertyName("eta")] + public float? Eta { get; set; } + + [JsonPropertyName("shifted_timestep")] + public int ShiftedTimestep { get; set; } + + [JsonPropertyName("custom_sigmas")] + public List CustomSigmas { get; set; } = []; + + [JsonPropertyName("flow_shift")] + public float? FlowShift { get; set; } + + [JsonPropertyName("guidance")] + public GuidanceParams Guidance { get; set; } + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/VaeTilingParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/VaeTilingParams.cs new file mode 100644 index 0000000..ddcb96d --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/VaeTilingParams.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public record VaeTilingParams + { + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + [JsonPropertyName("temporal_tiling")] + public bool TemporalTiling { get; set; } + + [JsonPropertyName("tile_size_x")] + public int TileSizeX { get; set; } + + [JsonPropertyName("tile_size_y")] + public int TileSizeY { get; set; } + + [JsonPropertyName("target_overlap")] + public float TargetOverlap { get; set; } = 0.5f; + + [JsonPropertyName("rel_size_x")] + public float RelSizeX { get; set; } + + [JsonPropertyName("rel_size_y")] + public float RelSizeY { get; set; } + + [JsonPropertyName("extra_tiling_args")] + public string ExtraTilingArgs { get; set; } = ""; + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Common/VideoParams.cs b/Apps/Amuse.Host.StableDiffusionCpp/Common/VideoParams.cs new file mode 100644 index 0000000..0c51b80 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Common/VideoParams.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Amuse.Host.StableDiffusionCpp.Common +{ + public class VideoParams + { + [JsonPropertyName("prompt")] + public string Prompt { get; set; } + + [JsonPropertyName("negative_prompt")] + public string NegativePrompt { get; set; } + + [JsonPropertyName("width")] + public int Width { get; set; } + + [JsonPropertyName("height")] + public int Height { get; set; } + + [JsonPropertyName("seed")] + public int Seed { get; set; } + + [JsonPropertyName("strength")] + public float Strength { get; set; } = 1f; + + [JsonPropertyName("clip_skip")] + public int ClipSkip { get; set; } = -1; + + [JsonPropertyName("video_frames")] + public int Frames { get; set; } = 33; + + [JsonPropertyName("fps")] + public int FrameRate { get; set; } = 16; + + [JsonPropertyName("moe_boundary")] + public float MoeBoundary { get; set; } = 0.875f; + + [JsonPropertyName("vace_strength")] + public float VaceStrength { get; set; } = 1f; + + [JsonPropertyName("init_image")] + public string ImageFirst { get; set; } + + [JsonPropertyName("end_image")] + public string ImageLast { get; set; } + + [JsonPropertyName("control_frames")] + public List ControlFrames { get; set; } + + [JsonPropertyName("sample_params")] + public SampleParams SampleParams { get; set; } + + [JsonPropertyName("high_noise_sample_params")] + public SampleParams SampleParamsHighNoise { get; set; } + + [JsonPropertyName("lora")] + public List Lora { get; set; } = []; + + [JsonPropertyName("vae_tiling_params")] + public VaeTilingParams VaeTilingParams { get; set; } + + [JsonPropertyName("output_format")] + public string OutputFormat { get; set; } = "webm"; + + [JsonPropertyName("output_compression")] + public int OutputCompression { get; set; } = 100; + + [JsonPropertyName("auto_resize_ref_image")] + public bool AutoResizeRefImage { get; set; } + + [JsonPropertyName("increase_ref_index")] + public bool IncreaseRefIndex { get; set; } + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Config/ModelConfig.cs b/Apps/Amuse.Host.StableDiffusionCpp/Config/ModelConfig.cs new file mode 100644 index 0000000..91a3613 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Config/ModelConfig.cs @@ -0,0 +1,24 @@ +namespace Amuse.Host.StableDiffusionCpp.Config +{ + public class ModelConfig + { + public string Full { get; set; } + public string ClipL { get; set; } + public string ClipG { get; set; } + public string ClipVison { get; set; } + public string T5XXL { get; set; } + public string LLM { get; set; } + public string VisionLLM { get; set; } + public string Diffusion { get; set; } + public string DiffusionHighNoise { get; set; } + public string DiffusionUncond { get; set; } + public string Connectors { get; set; } + public string Vae { get; set; } + public string VaeAudio { get; set; } + public string Tased { get; set; } + public string ControlNet { get; set; } + public string EmbeddingsDirectory { get; set; } + public string LoraModelDirectory { get; set; } + } + +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Config/ServerConfig.cs b/Apps/Amuse.Host.StableDiffusionCpp/Config/ServerConfig.cs new file mode 100644 index 0000000..08afbc7 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Config/ServerConfig.cs @@ -0,0 +1,22 @@ +using Amuse.Common; +using Amuse.Host.StableDiffusionCpp.Common; +using System.Collections.Generic; + +namespace Amuse.Host.StableDiffusionCpp.Config +{ + public record ServerConfig + { + public short Port { get; set; } = 1234; + public string Address { get; set; } = "127.0.0.1"; + public string BaseUrl => $"http://{Address}:{Port}/"; + public Dictionary ServerVariables { get; set; } + public ModelConfig ModelConfig { get; set; } + + // Device/Memory + public int DeviceId { get; set; } + public BackendType Backend { get; set; } + public int MemoryReserve { get; set; } = 1; + public MemoryModeType MemoryMode { get; set; } + public bool IsFlashAttentionEnabled { get; set; } + } +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/HostServer.cs b/Apps/Amuse.Host.StableDiffusionCpp/HostServer.cs new file mode 100644 index 0000000..9f38cfd --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/HostServer.cs @@ -0,0 +1,158 @@ +using Amuse.Common; +using Amuse.Common.Config; +using Amuse.Common.Message; +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using TensorStack.Common.Pipeline; + + +namespace Amuse.Host.StableDiffusionCpp +{ + public sealed class HostServer : PipelineServer + { + private readonly IProgress _progressRelayRunCallback; + + private IPipeline _pipeline; + private PipelineLoadOptions _pipelineOptions; + + public HostServer(ServerConfig channelConfig, ILogger logger) + : base(channelConfig, logger) + { + _progressRelayRunCallback = new Progress(async (p) => await UpdateProgress(p)); + } + + + /// + /// Called when the Channel is opened. + /// + /// Task. + protected override Task ChannelOpenedAsync() + { + return Task.CompletedTask; + } + + + /// + /// Called when the Channel is closed. + /// + protected override Task ChannelClosedAsync() + { + _pipeline?.Dispose(); + return Task.CompletedTask; + } + + + protected override async Task CreatePipelineAsync(PipelineRequest request, CancellationToken cancellationToken) + { + try + { + var timestamp = Stopwatch.GetTimestamp(); + var environmentRequest = request.CreateOptions; + + Logger.LogInformation($"[PipelineServer] [CreatePipeline] Environment created, Elapsed: {Stopwatch.GetElapsedTime(timestamp)}"); + await SendResponse(cancellationToken); + } + catch (Exception ex) + { + Logger.LogError(ex, "[PipelineServer] [CreatePipeline] An exception occurred creating environment."); + await SendException(ex, cancellationToken); + } + } + + + protected override async Task LoadPipelineAsync(PipelineRequest request, CancellationToken cancellationToken) + { + try + { + _pipelineOptions = request.LoadOptions; + + //TODO: Create Pipeline + + await _pipeline.LoadAsync(cancellationToken); + await SendResponse(cancellationToken); + + } + catch (Exception ex) + { + Logger.LogError(ex, "[PipelineServer] [LoadPipeline] An exception occurred loading pipeline."); + await SendException(ex, cancellationToken); + } + } + + + protected override async Task ReloadPipelineAsync(PipelineRequest request, CancellationToken cancellationToken) + { + try + { + var reloadOptions = request.ReloadOptions; + _pipelineOptions.ProcessType = reloadOptions.ProcessType; + _pipelineOptions.ControlNet = reloadOptions.ControlNet; + _pipelineOptions.LoraAdapters = reloadOptions.LoraAdapters; + + // TODO: Reload Pipeline + + await SendResponse(cancellationToken); + } + catch (Exception ex) + { + Logger.LogError(ex, "[PipelineServer] [ReloadPipeline] An exception occurred reloading pipeline."); + await SendException(ex, cancellationToken); + } + } + + + protected override async Task UnloadPipelineAsync(PipelineRequest request, CancellationToken cancellationToken) + { + try + { + await _pipeline.UnloadAsync(); + await SendResponse(cancellationToken); + } + catch (Exception ex) + { + Logger.LogError(ex, "[PipelineServer] [UnloadPipeline] An exception occurred unloading pipeline."); + await SendException(ex, cancellationToken); + } + } + + + protected override async Task RunPipelineAsync(PipelineRequest request, CancellationToken cancellationToken) + { + try + { + request.RunOptions.UnpackTensors(request); + + // TODO: Execute Image/Video + + } + catch (OperationCanceledException ex) + { + Logger.LogError("[PipelineServer] [RunPipeline] {Message}", ex.Message); + await SendException(ex, cancellationToken); + } + catch (Exception ex) + { + Logger.LogError(ex, "[PipelineServer] [RunPipeline] An exception occurred running pipeline."); + await SendException(ex, cancellationToken); + } + } + + + private async Task UpdateProgress(RunProgress progress) + { + await QueueProgress(new PipelineProgress + { + Key = "Generate", + Subkey = "Step", + Value = progress.Value, + Maximum = progress.Maximum, + Message = progress.Message, + Elapsed = (float)progress.Elapsed.TotalMilliseconds + }); + } + + } +} \ No newline at end of file diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Icon.ico b/Apps/Amuse.Host.StableDiffusionCpp/Icon.ico new file mode 100644 index 0000000..aa4f669 Binary files /dev/null and b/Apps/Amuse.Host.StableDiffusionCpp/Icon.ico differ diff --git a/Apps/Amuse.Host.StableDiffusionCpp/Program.cs b/Apps/Amuse.Host.StableDiffusionCpp/Program.cs new file mode 100644 index 0000000..b689aa4 --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/Program.cs @@ -0,0 +1,119 @@ +using Amuse.Common; +using Amuse.Common.Config; +using Microsoft.Extensions.Logging; +using Serilog; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using TensorStack.Common; +using Logger = Microsoft.Extensions.Logging.ILogger; + +namespace Amuse.Host.StableDiffusionCpp +{ + internal class Program + { + private static Logger _logger; + private static string _directoryBase; + private static string _directoryData; + private static ServerConfig _serverConfig; + + /// + /// Defines the entry point of the application. + /// + /// The arguments. + static async Task Main(string[] args) + { + if (!Enum.TryParse(args[0], true, out var serverType)) + throw new InvalidOperationException("Invalid ServerType"); + + _directoryBase = AppDomain.CurrentDomain.BaseDirectory; + _directoryData = GetApplicationDataDirectory(); + _serverConfig = ServerConfig.GetConfig(serverType, _directoryData); + + using (var mutex = new Mutex(true, $"Global\\{_serverConfig.Name}", out var createdNew)) + { + if (!createdNew) + throw new InvalidOperationException("Another instance of this application is already running."); + + _logger = ConfigureLogging(); + await StartAsync(); + } + } + + + /// + /// Start the server + /// + /// The options. + /// A Task representing the asynchronous operation. + private static async Task StartAsync() + { + try + { + _logger.LogInformation("[StartAsync] Starting {Name}...", _serverConfig.Name); + using (var cancellationTokenSource = new CancellationTokenSource()) + { + AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => cancellationTokenSource.SafeCancel(); + + // Start Server + using (var hostServer = new HostServer(_serverConfig, _logger)) + { + await hostServer.StartAsync(cancellationTokenSource.Token); + } + } + _logger.LogInformation("[StartAsync] {Name} stopped.", _serverConfig.Name); + } + catch (EndOfStreamException) + { + } + catch (Exception ex) + { + _logger.LogError(ex, "[StartAsync] An unhandled exception occurred."); + } + } + + + /// + /// Configures the logging. + /// + private static Logger ConfigureLogging() + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.File(GetLogName(), rollOnFileSizeLimit: true) + .CreateLogger(); + var factory = LoggerFactory.Create(builder => + { + builder.ClearProviders(); + builder.AddSerilog(dispose: true); + builder.AddConsole(); + builder.SetMinimumLevel(LogLevel.Trace); + }); + return factory.CreateLogger(); + } + + + /// + /// Gets the application data directory. + /// + private static string GetApplicationDataDirectory() + { +#if RELEASE_INSTALLER + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Amuse"); +#else + return _directoryBase; +#endif + } + + + /// + /// Gets the name of the log. + /// + private static string GetLogName() + { + var now = DateTime.Now; + return Path.Combine(_directoryData, @$"Logs\{_serverConfig.Name}-{DateTime.Now:dd-MM-yyyy}-{now.Hour * 3600 + now.Minute * 60 + now.Second}.txt"); + } + } +} diff --git a/Apps/Amuse.Host.StableDiffusionCpp/StableDiffusionClient.cs b/Apps/Amuse.Host.StableDiffusionCpp/StableDiffusionClient.cs new file mode 100644 index 0000000..5f931ab --- /dev/null +++ b/Apps/Amuse.Host.StableDiffusionCpp/StableDiffusionClient.cs @@ -0,0 +1,127 @@ +using Amuse.Host.StableDiffusionCpp.Common; +using Amuse.Host.StableDiffusionCpp.Config; +using System; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + +namespace Amuse.Host.StableDiffusionCpp +{ + internal sealed class StableDiffusionClient : IDisposable + { + private readonly HttpClient _httpClient; + private readonly ServerConfig _configuration; + private readonly JsonSerializerOptions _serializerOptions; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public StableDiffusionClient(ServerConfig configuration) + { + _configuration = configuration; + _httpClient = new HttpClient + { + BaseAddress = new Uri(_configuration.BaseUrl) + }; + _serializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + Converters = { new JsonStringEnumConverter() } + }; + } + + + /// + /// Get model capabilities + /// + /// The cancellation token. + public async Task GetCapabilitiesAsync(CancellationToken cancellationToken = default) + { + const string endpoint = "sdcpp/v1/capabilities"; + using (var response = await _httpClient.GetAsync(endpoint, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + return await response.Content.ReadFromJsonAsync(_serializerOptions, cancellationToken: cancellationToken); + } + } + + + /// + /// Get existing job + /// + /// The job. + /// The cancellation token. + public async Task GetJobAsync(JobModel job, CancellationToken cancellationToken = default) + { + const string endpoint = "sdcpp/v1/jobs/{0}"; + using (var response = await _httpClient.GetAsync(string.Format(endpoint, job.Id), cancellationToken)) + { + response.EnsureSuccessStatusCode(); + return await response.Content.ReadFromJsonAsync(_serializerOptions, cancellationToken: cancellationToken); + } + } + + + /// + /// Create new Image job + /// + /// The parameters. + /// The cancellation token. + public async Task CreateJobAsync(ImageParams parameters, CancellationToken cancellationToken = default) + { + const string endpoint = "sdcpp/v1/img_gen"; + using (var response = await _httpClient.PostAsJsonAsync(endpoint, parameters, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + return await response.Content.ReadFromJsonAsync(_serializerOptions, cancellationToken: cancellationToken); + } + } + + + /// + /// Create new Video job + /// + /// The parameters. + /// The cancellation token. + public async Task CreateJobAsync(VideoParams parameters, CancellationToken cancellationToken = default) + { + const string endpoint = "sdcpp/v1/vid_gen"; + using (var response = await _httpClient.PostAsJsonAsync(endpoint, parameters, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + return await response.Content.ReadFromJsonAsync(_serializerOptions, cancellationToken: cancellationToken); + + } + } + + + /// + /// Cancel the specified job + /// + /// The job. + /// The cancellation token. + public async Task CancelJobAsync(JobModel job, CancellationToken cancellationToken = default) + { + const string endpoint = "sdcpp/v1/jobs/{0}/cancel"; + using (var response = await _httpClient.GetAsync(string.Format(endpoint, job.Id), cancellationToken)) + { + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + } + } + + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _httpClient?.Dispose(); + } + + } +} diff --git a/TensorStack.Common/Common/BackendType.cs b/TensorStack.Common/Common/BackendType.cs index b3af3bd..193ca20 100644 --- a/TensorStack.Common/Common/BackendType.cs +++ b/TensorStack.Common/Common/BackendType.cs @@ -7,7 +7,10 @@ public enum BackendType [Display(Name = "OnnxRuntime", ShortName = "Onnx", Description = "OnnxRuntime .NET model inference using TensorStack")] OnnxRuntime = 0, - [Display(Name = "PyTorch", ShortName = "Onnx", Description = "PyTorch model inference using HuggingFace Diffusers & Transformers")] - PyTorch = 10 + [Display(Name = "PyTorch", ShortName = "torch", Description = "PyTorch model inference using HuggingFace Diffusers & Transformers")] + PyTorch = 10, + + [Display(Name = "StableDiffusionCpp", ShortName = "SD.cpp", Description = "GGML model inference using StableDiffusionCpp")] + StableDiffusionCpp = 20 } } diff --git a/TensorStack.sln b/TensorStack.sln index ab509c4..ea7b0fb 100644 --- a/TensorStack.sln +++ b/TensorStack.sln @@ -56,6 +56,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amuse.Host.Onnx", "Apps\Amu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amuse.Host.PyTorch", "Apps\Amuse.Host.PyTorch\Amuse.Host.PyTorch.csproj", "{E3E2CB70-0145-7F05-D936-3CE7DB5740FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amuse.Host.StableDiffusionCpp", "Apps\Amuse.Host.StableDiffusionCpp\Amuse.Host.StableDiffusionCpp.csproj", "{DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugApp", "Apps\DebugApp\DebugApp.csproj", "{CD6962D3-18EA-4113-25AE-EF9BC1FBD4FE}" EndProject Global @@ -191,6 +193,12 @@ Global {E3E2CB70-0145-7F05-D936-3CE7DB5740FF}.Release_Installer|Any CPU.Build.0 = Release_Installer|Any CPU {E3E2CB70-0145-7F05-D936-3CE7DB5740FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3E2CB70-0145-7F05-D936-3CE7DB5740FF}.Release|Any CPU.Build.0 = Release|Any CPU + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}.Release_Installer|Any CPU.ActiveCfg = Release|Any CPU + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}.Release_Installer|Any CPU.Build.0 = Release|Any CPU + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2}.Release|Any CPU.Build.0 = Release|Any CPU {CD6962D3-18EA-4113-25AE-EF9BC1FBD4FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD6962D3-18EA-4113-25AE-EF9BC1FBD4FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD6962D3-18EA-4113-25AE-EF9BC1FBD4FE}.Release_Installer|Any CPU.ActiveCfg = Release|Any CPU @@ -206,6 +214,7 @@ Global {EB356593-64EA-142D-AA48-63BF20AC1343} = {F81721F7-8D43-410F-A452-99A4F51AB2A0} {B44ED101-BA40-6245-0389-71F72289FB4B} = {F81721F7-8D43-410F-A452-99A4F51AB2A0} {E3E2CB70-0145-7F05-D936-3CE7DB5740FF} = {F81721F7-8D43-410F-A452-99A4F51AB2A0} + {DEAD9EB8-5CDF-46ED-A612-65F88E5417C2} = {F81721F7-8D43-410F-A452-99A4F51AB2A0} {CD6962D3-18EA-4113-25AE-EF9BC1FBD4FE} = {F81721F7-8D43-410F-A452-99A4F51AB2A0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution