Expose latent upscale sampler controls

This commit is contained in:
2026-09-03 13:05:32 +00:00
parent c1d937e0e2
commit 5c12cd18a3
5 changed files with 126 additions and 52 deletions
+58 -13
View File
@@ -4,6 +4,7 @@ import math
import os
import re
import comfy.samplers
import folder_paths
import torch
@@ -33,6 +34,8 @@ LATENTS_STD = [
]
_LATENT_UPSCALE_FOLDER = "latent_upscale_models"
MP_UNIT = 1024 * 1024
RES_MULTIPLE = 32
def _models_dir():
@@ -347,10 +350,29 @@ def _compute_upscale_target(width, height, h_in, w_in):
return h_out, w_out, eff
def _scale_to_megapixels(w, h, mp, multiple=RES_MULTIPLE):
if not mp or mp <= 0 or w <= 0 or h <= 0:
return int(h), int(w)
multiple = max(1, int(multiple))
scale = math.sqrt((float(mp) * MP_UNIT) / float(w * h))
nw = max(multiple, int(round(w * scale / multiple)) * multiple)
nh = max(multiple, int(round(h * scale / multiple)) * multiple)
return nh, nw
def _resolve_target_size(param, h_in, w_in):
width = int(param.get("width", 0) or 0)
height = int(param.get("height", 0) or 0)
megapixels = float(param.get("megapixels", 0.0) or 0.0)
if width > 0 and height > 0:
return height, width
if megapixels > 0:
return _scale_to_megapixels(w_in, h_in, megapixels)
return int(h_in), int(w_in)
def upscale_video_model(video, param):
model_name = param["model_name"]
width = int(param["width"])
height = int(param["height"])
device = param.get("device", "cuda")
precision = param.get("precision", "fp16")
@@ -359,7 +381,8 @@ def upscale_video_model(video, param):
compute_dtype = {"fp32": torch.float32, "fp16": torch.float16, "bf16": torch.bfloat16}[precision]
_, c, t, h_in, w_in = video.shape
h_out, w_out, eff = _compute_upscale_target(width, height, h_in, w_in)
h_out, w_out = _resolve_target_size(param, h_in, w_in)
eff = (w_out / float(w_in) + h_out / float(h_in)) / 2.0 if w_in and h_in else 1.0
if eff < 1.0 and (w_out < w_in or h_out < h_in):
raise ValueError("This model only supports upscaling (effective scale >= 1.0).")
@@ -386,10 +409,8 @@ def upscale_video_model(video, param):
def upscale_video_interp(video, param):
method = str(param.get("method") or "bilinear")
width = int(param.get("width", 0) or 0)
height = int(param.get("height", 0) or 0)
_, c, t, h_in, w_in = video.shape
h_out, w_out, _ = _compute_upscale_target(width, height, h_in, w_in)
h_out, w_out = _resolve_target_size(param, h_in, w_in)
if h_out == h_in and w_out == w_in:
return video, h_in, w_in
video_bt = video.permute(0, 2, 1, 3, 4).reshape(-1, c, h_in, w_in)
@@ -425,23 +446,42 @@ class H3LatentUpscaleParams:
"method": (["nearest-exact", "bilinear", "area", "bicubic"], {"default": "bilinear",
"tooltip": "Interpolation method used when mode = interp."}),
"width": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 32,
"tooltip": "Target upscaled pixel width for the latent refinement stage. 0 keeps the original width and effectively disables the resize."}),
"tooltip": "Explicit target width for the latent refinement stage. Leave at 0 to let megapixels choose the size instead."}),
"height": ("INT", {"default": 0, "min": 0, "max": 4096, "step": 32,
"tooltip": "Target upscaled pixel height for the latent refinement stage. 0 keeps the original height and effectively disables the resize."}),
"tooltip": "Explicit target height for the latent refinement stage. Leave at 0 to let megapixels choose the size instead."}),
"device": (["cuda", "cpu"], {"default": "cuda",
"tooltip": "Device used by the H3 latent upscaler model when mode = model."}),
"precision": (["fp16", "fp32", "bf16"], {"default": "fp16",
"tooltip": "Computation precision used by the H3 latent upscaler model when mode = model."}),
"refine_denoise": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01,
"tooltip": "How much the 2-step refinement pass may rewrite the upscaled latent. Lower = safer, higher = freer."}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler_ancestral",
"tooltip": "Sampler used for the latent refinement pass. Default matches the current H3 preference."}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "simple",
"tooltip": "Scheduler used for the latent refinement pass."}),
"steps": ("INT", {"default": 2, "min": 1, "max": 50, "step": 1,
"tooltip": "Number of refinement steps applied after the latent upscaler stage."}),
"denoise": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0, "step": 0.01,
"tooltip": "How much the refinement pass may rewrite the upscaled latent. Lower = safer, higher = freer."}),
"megapixels": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 4.0, "step": 0.01,
"tooltip": "Primary target size for the latent refinement stage. If width and height are both set, they win; otherwise the node scales the current shot to this pixel budget while preserving aspect ratio. 0 keeps the incoming latent size."}),
}
}
def build(self, mode, model_name, method, width, height, device, precision, refine_denoise):
def build(self, mode, model_name, method, width, height, device, precision, sampler_name, scheduler, steps, denoise, megapixels):
width = int(width)
height = int(height)
steps = int(steps)
if mode == "off":
return ({"mode": "off", "width": width, "height": height, "refine_denoise": float(refine_denoise)},)
return ({
"mode": "off",
"width": width,
"height": height,
"sampler_name": sampler_name,
"scheduler": scheduler,
"steps": steps,
"denoise": float(denoise),
"refine_denoise": float(denoise),
"megapixels": float(megapixels),
},)
if width > 0:
width = int(round(width / 32.0)) * 32
if height > 0:
@@ -454,7 +494,12 @@ class H3LatentUpscaleParams:
"height": height,
"device": device,
"precision": precision,
"refine_denoise": float(refine_denoise),
"sampler_name": sampler_name,
"scheduler": scheduler,
"steps": steps,
"denoise": float(denoise),
"refine_denoise": float(denoise),
"megapixels": float(megapixels),
},)