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