Skip to content
4 changes: 4 additions & 0 deletions docs/Features/Upscaling.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
(TODO)

Downloads here: <https://huggingface.co/Comfy-Org/PixelDiT/tree/main/diffusion_models>

# SeedVR2

Downloads here: <https://huggingface.co/Comfy-Org/SeedVR2/tree/main/diffusion_models>
1 change: 0 additions & 1 deletion launchtools/extension_list.fds
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,6 @@ SeedVR2Upscaler:
description: Adds SeedVR2 AI upscaler with auto VRAM detection, quality presets, and 2-step upscaling for reduced artifacts.
url: https://github.com/rlerdorf/SwarmUI-SeedVR2Upscaler
license: MIT
ci-test: true
tags:
- parameters
- nodes
Expand Down
34 changes: 29 additions & 5 deletions src/BuiltinExtensions/ComfyUIBackend/ComfyUIBackendExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -610,15 +610,17 @@ public static void AssignValuesFromRaw(JObject rawObjectInfo)
}
}

public static T2IRegisteredParam<string> CustomWorkflowParam, SamplerParam, SchedulerParam, RefinerSamplerParam, RefinerSchedulerParam, RefinerUpscaleMethod, UseIPAdapterForRevision, IPAdapterWeightType, VideoPreviewType, VideoFrameInterpolationMethod, GligenModel, YoloModelInternal, PreferredDType, UseStyleModel, TeaCacheMode, EasyCacheMode, SetClipDevice, ModelAttentionBackend;
public static T2IRegisteredParam<string> CustomWorkflowParam, SamplerParam, SchedulerParam, RefinerSamplerParam, RefinerSchedulerParam, RefinerUpscaleMethod, UseIPAdapterForRevision, IPAdapterWeightType, VideoPreviewType, VideoFrameInterpolationMethod, GligenModel, YoloModelInternal, PreferredDType, UseStyleModel, TeaCacheMode, EasyCacheMode, SetClipDevice, ModelAttentionBackend, SeedVRUpscaleMethod, SeedVRColorCorrectionBehavior;

public static T2IRegisteredParam<bool> AITemplateParam, DebugRegionalPrompting, ShiftedLatentAverageInit, UseCfgZeroStar, UseTCFG;
public static T2IRegisteredParam<bool> AITemplateParam, DebugRegionalPrompting, ShiftedLatentAverageInit, UseCfgZeroStar, UseTCFG, SeedVRSplitLatent;

public static T2IRegisteredParam<double> IPAdapterWeight, IPAdapterStart, IPAdapterEnd, SelfAttentionGuidanceScale, SelfAttentionGuidanceSigmaBlur, PerturbedAttentionGuidanceScale, StyleModelMergeStrength, StyleModelApplyStart, StyleModelMultiplyStrength, RescaleCFGMultiplier, TeaCacheThreshold, TeaCacheStart, NunchakuCacheThreshold, EasyCacheThreshold, EasyCacheStart, EasyCacheEnd, RenormCFG, NormalizedAttentionGuidanceScale, NormalizedAttentionGuidanceAlpha, NormalizedAttentionGuidanceTau;
public static T2IRegisteredParam<double> IPAdapterWeight, IPAdapterStart, IPAdapterEnd, SelfAttentionGuidanceScale, SelfAttentionGuidanceSigmaBlur, PerturbedAttentionGuidanceScale, StyleModelMergeStrength, StyleModelApplyStart, StyleModelMultiplyStrength, RescaleCFGMultiplier, TeaCacheThreshold, TeaCacheStart, NunchakuCacheThreshold, EasyCacheThreshold, EasyCacheStart, EasyCacheEnd, RenormCFG, NormalizedAttentionGuidanceScale, NormalizedAttentionGuidanceAlpha, NormalizedAttentionGuidanceTau, SeedVRUpscale;

public static T2IRegisteredParam<int> RefinerHyperTile, VideoFrameInterpolationMultiplier;
public static T2IRegisteredParam<int> RefinerHyperTile, VideoFrameInterpolationMultiplier, SeedVRTemporalVideoOverlap;

public static T2IRegisteredParam<T2IModel> PixelDecoderModel;
public static T2IRegisteredParam<T2IModel> PixelDecoderModel, SeedVRModel;

public static T2IParamGroup GroupSeedVR;

public static T2IRegisteredParam<string>[] ControlNetPreprocessorParams = new T2IRegisteredParam<string>[3], ControlNetUnionTypeParams = new T2IRegisteredParam<string>[3];

Expand Down Expand Up @@ -852,6 +854,28 @@ public override void OnInit()
ModelAttentionBackend = T2IParamTypes.Register<string>(new("Model Attention Backend", "Override which attention implementation the model uses.\n'pytorch attention' is the standard default.\n'comfy kitchen attention' is a new sage-like attention impl from Comfy directly that has better performance, but may not work on all machines.",
"pytorch attention", Group: T2IParamTypes.GroupAdvancedModelAddons, IsAdvanced: true, Toggleable: true, GetValues: (_) => ModelAttentionBackends, OrderPriority: 41
));
// ================================================ SeedVR ================================================
GroupSeedVR = new T2IParamGroup("SeedVR", Toggles: true, Open: false, OrderPriority: -2.5, Description: "SeedVR2 is a one-step restoration model, run over the result of the normal generation.");
SeedVRModel = T2IParamTypes.Register<T2IModel>(new("SeedVR Model", "Which SeedVR2 model to restore with.",
"None", IgnoreIf: "None", FeatureFlag: "comfyui", Group: GroupSeedVR, Subtype: "Stable-Diffusion", ChangeWeight: 9, DoNotPreview: true, OrderPriority: -10,
GetValues: (session) => ["None", .. T2IParamTypes.CleanModelList(Program.MainSDModels.ListModelsFor(session).Where(m => m.ModelClass?.CompatClass?.ID == "seedvr2").OrderBy(m => m.Name).Select(m => m.Name))]
));
SeedVRUpscale = T2IParamTypes.Register<double>(new("SeedVR Upscale", "Optional upscale of the image before SeedVR2 runs over it.\nSetting to '1' disables the upscale, and just restores at the current size.",
"1", IgnoreIf: "1", Min: 0.25, Max: 8, ViewMax: 4, Step: 0.25, OrderPriority: -9, ViewType: ParamViewType.SLIDER, FeatureFlag: "comfyui", Group: GroupSeedVR, DoNotPreview: true, Examples: ["1", "1.5", "2"]
));
SeedVRUpscaleMethod = T2IParamTypes.Register<string>(new("SeedVR Upscale Method", "How to upscale the image before SeedVR2 runs over it, if upscaling is used.",
"pixel-lanczos", OrderPriority: -8, FeatureFlag: "comfyui", Group: GroupSeedVR, ChangeWeight: 1,
GetValues: (session) => RefinerUpscaleMethod.Type.GetValues(session), DependNonDefault: SeedVRUpscale.Type.ID
));
SeedVRColorCorrectionBehavior = T2IParamTypes.Register<string>(new("SeedVR Color Correction Behavior", "How to match the colors of a SeedVR2 restore back to the image it was given.\n'None' = Do not attempt color correction, only align the geometry.\n'CIELAB' = Transfer the color in CIELAB space, preserving detail.\n'Wavelet' = Transfer the low-frequency color, keeping the upscaled high-frequency detail.\n'AdaIN' = Match the per-channel mean and standard deviation.",
"lab", FeatureFlag: "comfyui", Group: GroupSeedVR, IsAdvanced: true, OrderPriority: 1, GetValues: (_) => ["none///None", "lab///CIELAB", "wavelet///Wavelet", "adain///AdaIN"]
));
SeedVRSplitLatent = T2IParamTypes.Register<bool>(new("SeedVR Split Latent", "If enabled, samples a SeedVR2 video restore as chunks of frames instead of all at once, sized to fit in free VRAM.\nChunking reduces VRAM consumption.\nDoes nothing to a single image, or to a video that already fits.",
"false", IgnoreIf: "false", FeatureFlag: "comfyui", Group: GroupSeedVR, IsAdvanced: true, OrderPriority: 2
));
SeedVRTemporalVideoOverlap = T2IParamTypes.Register<int>(new("SeedVR Temporal Video Overlap", "How many latent frames of overlap to keep between 'SeedVR Split Latent' chunks.\nHigher overlap hides the chunk seams better but takes longer.",
"0", Min: 0, Max: 4096, Step: 1, IsAdvanced: true, FeatureFlag: "comfyui", Group: GroupSeedVR, OrderPriority: 3, DependNonDefault: SeedVRSplitLatent.Type.ID
));
BackendApiType = Program.Backends.RegisterBackendType<ComfyUIAPIBackend>("comfyui_api", "ComfyUI API By URL", "A backend powered by a pre-existing installation of ComfyUI, referenced via API base URL.", true);
BackendSelfStartType = Program.Backends.RegisterBackendType<ComfyUISelfStartBackend>("comfyui_selfstart", "ComfyUI Self-Starting", "A backend powered by a pre-existing installation of the ComfyUI, automatically launched and managed by this UI server.", isStandard: true);
SwarmSwarmBackend.ValidityChecks[BackendApiType.ID] = (backend, input) => ComfyUIAPIAbstractBackend.TryIsValid(input, backend.ExtensionData.GetValueOrDefault("ComfyNodeTypes", null) as HashSet<string>);
Expand Down
165 changes: 163 additions & 2 deletions src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ public string CreateKSampler(JArray model, JArray pos, JArray neg, JArray latent
defscheduler ??= "simple";
}
// TODO: Registry of model default preferences instead of this
else if (IsFlux() || IsWanVideo() || IsWanVideo22() || IsOmniGen() || IsQwenImage() || IsZImage() || IsZetaChroma() || IsErnie() || IsHiDreamO1() || IsLens() || IsPixelDiT() || IsKrea2() || IsBoogu() || IsMageFlow() || IsMiniMaxMusic3())
else if (IsFlux() || IsWanVideo() || IsWanVideo22() || IsOmniGen() || IsQwenImage() || IsZImage() || IsZetaChroma() || IsErnie() || IsHiDreamO1() || IsLens() || IsPixelDiT() || IsKrea2() || IsBoogu() || IsMageFlow() || IsMiniMaxMusic3() || IsSeedVR2())
{
defscheduler ??= "simple";
}
Expand Down Expand Up @@ -2118,8 +2118,13 @@ public void CreateImageToVideo(ImageToVideoGenInfo genInfo)
["upscale_method"] = "lanczos",
["crop"] = "disabled"
});
// TODO: Update width/height properly
Comment thread
mcmonkey4eva marked this conversation as resolved.
CurrentMedia = CurrentMedia.WithPath([scaled, 0]);
// TODO: Update width/height properly
if (genInfo.Width?.Type == JTokenType.Integer && genInfo.Height?.Type == JTokenType.Integer)
{
CurrentMedia.Width = (int)genInfo.Width;
CurrentMedia.Height = (int)genInfo.Height;
}
foreach (Action<ImageToVideoGenInfo> altHandler in AltImageToVideoPreHandlers)
{
altHandler(genInfo);
Expand Down Expand Up @@ -2796,6 +2801,162 @@ public WGNodeData CreatePixelDecode(T2IModel pidModel, WGNodeData media, WGNodeD
return result;
}

/// <summary>Creates a SeedVR2 restoration stage: converts to a SeedVR2-space latent and samples the restored image from it.</summary>
public WGNodeData CreateSeedVR2Restore(T2IModel seedVrModel, WGNodeData media, WGNodeData decodeVae, long seed, bool isRefiner = false)
{
WGNodeData raw = media.AsRawImage(decodeVae);
JArray resized = raw.Path;
string preprocessed = CreateNode("SeedVR2Preprocess", new JObject()
{
["resized_images"] = resized
});
T2IModel priorFinalModel = FinalLoadedModel;
List<T2IModel> priorFinalModelList = FinalLoadedModelList;
WGNodeData priorModel = CurrentModel, priorTextEnc = CurrentTextEnc, priorVae = CurrentVae;
bool priorNoVae = NoVAEOverride;
int sectionId = isRefiner ? T2IParamInput.SectionID_Refiner : T2IParamInput.SectionID_SeedVR;
FinalLoadedModel = seedVrModel;
FinalLoadedModelList = [seedVrModel];
NoVAEOverride = true;
(FinalLoadedModel, CurrentModel, CurrentTextEnc, CurrentVae) = CreateModelLoader(seedVrModel, "SeedVR2", sectionId: sectionId);
NoVAEOverride = priorNoVae;
WGNodeData encoded = raw.WithPath([preprocessed, 0]).EncodeToLatent(CurrentVae);
JArray latent = encoded.Path;
JArray chunkOverlap = null;
if (UserInput.Get(ComfyUIBackendExtension.SeedVRSplitLatent, false))
{
int overlap = UserInput.Get(ComfyUIBackendExtension.SeedVRTemporalVideoOverlap, 0);
string chunked = CreateNode("SeedVR2TemporalChunk", new JObject()
{
["latent"] = latent,
["temporal_overlap"] = overlap,
["chunking_mode"] = "auto"
});
latent = [chunked, 0];
chunkOverlap = [chunked, 1];
}
string cond = CreateNode("SeedVR2Conditioning", new JObject()
{
["model"] = CurrentModel.Path,
["vae_conditioning"] = latent
});
int steps = UserInput.GetNullable(T2IParamTypes.Steps, sectionId, false) ?? (isRefiner ? UserInput.GetNullable(T2IParamTypes.RefinerSteps) : null) ?? 1;
double cfg = UserInput.GetNullable(T2IParamTypes.CFGScale, sectionId, false) ?? (isRefiner ? UserInput.GetNullable(T2IParamTypes.RefinerCFGScale) : null) ?? 1;
string explicitSampler = UserInput.Get(ComfyUIBackendExtension.SamplerParam, null, sectionId: sectionId, includeBase: false) ?? (isRefiner ? UserInput.Get(ComfyUIBackendExtension.RefinerSamplerParam, null) : null);
string explicitScheduler = UserInput.Get(ComfyUIBackendExtension.SchedulerParam, null, sectionId: sectionId, includeBase: false) ?? (isRefiner ? UserInput.Get(ComfyUIBackendExtension.RefinerSchedulerParam, null) : null);
int startStep = isRefiner ? (int)Math.Round(steps * (1 - UserInput.Get(T2IParamTypes.RefinerControl, 1))) : 0;
string sampled = CreateKSampler(CurrentModel.Path, [cond, 0], [cond, 1], latent, cfg, steps, startStep, 10000, seed, false, true, explicitSampler: explicitSampler ?? "euler", explicitScheduler: explicitScheduler, sectionId: sectionId);
JArray sampledLatent = [sampled, 0];
if (chunkOverlap is not null)
{
string merged = CreateNode("SeedVR2TemporalMerge", new JObject()
{
["latents"] = sampledLatent,
["temporal_overlap"] = chunkOverlap
});
sampledLatent = [merged, 0];
}
WGNodeData decoded = encoded.WithPath(sampledLatent).DecodeLatents(CurrentVae, false);
string post = CreateNode("SeedVR2PostProcessing", new JObject()
{
["images"] = decoded.Path,
["original_resized_images"] = resized,
["color_correction_method"] = UserInput.Get(ComfyUIBackendExtension.SeedVRColorCorrectionBehavior, "lab")
});
WGNodeData result = raw.WithPath([post, 0]);
result.Width = raw.Width;
result.Height = raw.Height;
FinalLoadedModel = priorFinalModel;
FinalLoadedModelList = priorFinalModelList;
CurrentModel = priorModel;
CurrentTextEnc = priorTextEnc;
CurrentVae = priorVae;
return result;
}

/// <summary>Runs the SeedVR group's restoration pass over the current media.</summary>
public void RunSeedVR2Stage(WGNodeData vaeOverride = null)
{
if (!UserInput.TryGet(ComfyUIBackendExtension.SeedVRModel, out T2IModel seedVrModel) || seedVrModel is null)
{
return;
}
WGNodeData vae = vaeOverride ?? CurrentVae;
if (UserInput.Get(T2IParamTypes.OutputIntermediateImages, false))
{
CurrentMedia.SaveOutput(vae, CurrentAudioVae, GetStableDynamicID(50000, 0));
}
long seed = UserInput.Get(T2IParamTypes.Seed) + 500;
WGNodeData media = CurrentMedia;
double scale = UserInput.Get(ComfyUIBackendExtension.SeedVRUpscale, 1);
if (scale != 1)
{
// TODO: Should probably extract a shared upscale logic with the refiner rather than copy/pasted here
string method = UserInput.Get(ComfyUIBackendExtension.SeedVRUpscaleMethod, "pixel-lanczos");
int width = (((int)Math.Round((media.Width ?? UserInput.GetImageWidth()) * scale)) / 16) * 16;
int height = (((int)Math.Round((media.Height ?? UserInput.GetImageHeight()) * scale)) / 16) * 16;
if (method.StartsWith("latent-"))
{
media = media.AsLatentImage(vae);
string latentUpscaled = CreateNode("LatentUpscaleBy", new JObject()
{
["samples"] = media.Path,
["upscale_method"] = method.After("latent-"),
["scale_by"] = scale
});
media = media.WithPath([latentUpscaled, 0]);
media.Width = width;
media.Height = height;
}
else
{
media = media.AsRawImage(vae);
if (method.StartsWith("pidmodel-"))
{
T2IModel pidModel = ComfyUIBackendExtension.GetPidModel(method.After("pidmodel-"), UserInput.SourceSession);
media = CreatePixelDecode(pidModel, media, vae, seed);
}
else if (method.StartsWith("model-"))
{
string loaderNode = CreateNode("UpscaleModelLoader", new JObject()
{
["model_name"] = method.After("model-")
});
string upscaledNode = CreateNode("ImageUpscaleWithModel", new JObject()
{
["upscale_model"] = NodePath(loaderNode, 0),
["image"] = media.Path
});
media = media.WithPath([upscaledNode, 0]);
media.Width = null; // the model's own scale factor is unknown here, so always correct after
media.Height = null;
}
else if (method.StartsWith("pixel-"))
{
if (media.Width != width || media.Height != height)
{
string scaled = CreateNode("ImageScale", new JObject()
{
["image"] = media.Path,
["width"] = width,
["height"] = height,
["upscale_method"] = method.StartsWith("pixel-") ? method.After("pixel-") : "lanczos",
["crop"] = "disabled"
});
media = media.WithPath([scaled, 0]);
media.Width = width;
media.Height = height;
}
}
else
{
throw new SwarmReadableErrorException($"Unsupported upscale method '{method}' for SeedVR");
}
}
}
CurrentMedia = CreateSeedVR2Restore(seedVrModel, media, vae, seed);
}

/// <summary>Creates a "CLIPTextEncode" or equivalent node for the given input, applying prompt-given conditioning modifiers as relevant.</summary>
public JArray CreateConditioning(string prompt, JArray clip, T2IModel model, bool isPositive, string firstId = null, bool isRefiner = false, bool isVideo = false, bool isVideoSwap = false, bool isPixelDecoder = false, JArray attachImages = null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ public bool IsKontext()
/// <summary>Returns true if the current model is PiD.</summary>
public bool IsPiD() => IsModelCompatClass(T2IModelClassSorter.CompatPiD);

/// <summary>Returns true if the current model is SeedVR2.</summary>
public bool IsSeedVR2() => IsModelCompatClass(T2IModelClassSorter.CompatSeedVR2);

/// <summary>Returns true if the current model is HiDream-i1.</summary>
public bool IsHiDream() => IsModelCompatClass(T2IModelClassSorter.CompatHiDreamI1);

Expand Down Expand Up @@ -1047,7 +1050,7 @@ public void LoadClip3(string type, string modelA, string modelB, string modelC)
{
dtype = "default";
}
else if (IsZImage() || IsZetaChroma() || IsAnima() || IsLens() || IsPixelDiT() || IsPiD()) // Model is small and dense, so trust user preferred download format
else if (IsZImage() || IsZetaChroma() || IsAnima() || IsLens() || IsPixelDiT() || IsPiD() || IsSeedVR2()) // Model is small and dense, so trust user preferred download format
{
dtype = "default";
}
Expand Down Expand Up @@ -1314,6 +1317,10 @@ public void LoadClip3(string type, string modelA, string modelB, string modelC)
helpers.LoadClip("pixeldit", helpers.GetGemma2_2bElmModel());
LoadingVAE = CreateVAELoader("pixel_space");
}
else if (IsSeedVR2())
{
helpers.DoVaeLoader(null, T2IModelClassSorter.CompatSeedVR2, "seedvr2-vae");
}
else if (IsHiDream())
{
string loaderType = "QuadrupleCLIPLoader";
Expand Down Expand Up @@ -1594,7 +1601,7 @@ public void LoadClip3(string type, string modelA, string modelB, string modelC)
{
step.Action(this);
}
if (LoadingClip is null)
if (LoadingClip is null && type != "SeedVR2")
{
if (string.IsNullOrWhiteSpace(model.Metadata?.ModelClassType))
{
Expand Down
Loading
Loading