Add experimental scheduler node with sigma plot

This commit is contained in:
kijai
2025-08-25 14:53:36 +03:00
parent d9def84332
commit 288a7e96a9
3 changed files with 313 additions and 208 deletions
@@ -611,6 +611,7 @@
},
{
"name": "image_2",
"shape": 7,
"type": "IMAGE",
"link": 152
},
@@ -662,6 +663,7 @@
},
{
"name": "image_2",
"shape": 7,
"type": "IMAGE",
"link": 150
}
@@ -806,7 +808,14 @@
"flags": {},
"order": 9,
"mode": 0,
"inputs": [],
"inputs": [
{
"name": "compile_args",
"shape": 7,
"type": "WANCOMPILEARGS",
"link": null
}
],
"outputs": [
{
"name": "vae",
@@ -903,7 +912,7 @@
],
"size": [
887.1368408203125,
934.646484375
334
],
"flags": {},
"order": 38,
@@ -988,6 +997,7 @@
"inputs": [
{
"name": "vae",
"shape": 7,
"type": "WANVAE",
"link": 170
},
@@ -1095,6 +1105,7 @@
"inputs": [
{
"name": "t5",
"shape": 7,
"type": "WANTEXTENCODER",
"link": 15
},
@@ -1123,7 +1134,9 @@
"widgets_values": [
"CG动画风格,一只蓝色的小鸟从地面起飞,煽动翅膀。小鸟羽毛细腻,胸前有独特的花纹,背景是蓝天白云,阳光明媚。镜跟随小鸟向上移动,展现出小鸟飞翔的姿态和天空的广阔。近景,仰视视角",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
true
true,
false,
"gpu"
],
"color": "#332922",
"bgcolor": "#593930"
@@ -1219,7 +1232,7 @@
],
"size": [
315,
154
202
],
"flags": {},
"order": 11,
@@ -1245,7 +1258,9 @@
false,
false,
true,
0
0,
0,
false
],
"color": "#223",
"bgcolor": "#335"
@@ -1477,7 +1492,8 @@
"0, 0, 0",
"center",
16,
"cpu"
"cpu",
"<tr><td>Output: </td><td><b>1</b> x <b>640</b> x <b>640 | 4.69MB</b></td></tr>"
]
},
{
@@ -1489,7 +1505,7 @@
],
"size": [
270,
286
336
],
"flags": {},
"order": 28,
@@ -1564,9 +1580,199 @@
"0, 0, 0",
"center",
16,
"cpu"
"cpu",
"<tr><td>Output: </td><td><b>1</b> x <b>640</b> x <b>640 | 4.69MB</b></td></tr>"
]
},
{
"id": 106,
"type": "WanVideoLoraSelect",
"pos": [
-336.7720642089844,
-698.3348999023438
],
"size": [
424.9496765136719,
150
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "prev_lora",
"shape": 7,
"type": "WANVIDLORA",
"link": null
},
{
"name": "blocks",
"shape": 7,
"type": "SELECTEDBLOCKS",
"link": null
}
],
"outputs": [
{
"name": "lora",
"type": "WANVIDLORA",
"links": [
179
]
}
],
"properties": {
"cnr_id": "ComfyUI-WanVideoWrapper",
"ver": "974dd656dab305f7fa122cca435759105ea44488",
"Node name for S&R": "WanVideoLoraSelect"
},
"widgets_values": [
"Wan21_T2V_14B_lightx2v_cfg_step_distill_lora_rank32.safetensors",
1.2000000000000002,
false,
true
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 35,
"type": "WanVideoTorchCompileSettings",
"pos": [
-307.4797058105469,
-1197.4749755859375
],
"size": [
421.6000061035156,
202
],
"flags": {},
"order": 18,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "torch_compile_args",
"type": "WANCOMPILEARGS",
"slot_index": 0,
"links": [
190
]
}
],
"properties": {
"cnr_id": "ComfyUI-WanVideoWrapper",
"ver": "d9b1f4d1a5aea91d101ae97a54714a5861af3f50",
"Node name for S&R": "WanVideoTorchCompileSettings"
},
"widgets_values": [
"inductor",
false,
"default",
false,
64,
true,
128
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 22,
"type": "WanVideoModelLoader",
"pos": [
119.37029266357422,
-926.8419799804688
],
"size": [
477.4410095214844,
314
],
"flags": {},
"order": 24,
"mode": 0,
"inputs": [
{
"name": "compile_args",
"shape": 7,
"type": "WANCOMPILEARGS",
"link": 190
},
{
"name": "block_swap_args",
"shape": 7,
"type": "BLOCKSWAPARGS",
"link": 174
},
{
"name": "lora",
"shape": 7,
"type": "WANVIDLORA",
"link": 179
},
{
"name": "vram_management_args",
"shape": 7,
"type": "VRAM_MANAGEMENTARGS",
"link": null
},
{
"name": "extra_model",
"shape": 7,
"type": "VACEPATH",
"link": null
},
{
"name": "fantasytalking_model",
"shape": 7,
"type": "FANTASYTALKINGMODEL",
"link": null
},
{
"name": "multitalk_model",
"shape": 7,
"type": "MULTITALKMODEL",
"link": null
},
{
"name": "fantasyportrait_model",
"shape": 7,
"type": "FANTASYPORTRAITMODEL",
"link": null
},
{
"name": "vace_model",
"shape": 7,
"type": "VACEPATH",
"link": null
}
],
"outputs": [
{
"name": "model",
"type": "WANVIDEOMODEL",
"slot_index": 0,
"links": [
29,
103
]
}
],
"properties": {
"cnr_id": "ComfyUI-WanVideoWrapper",
"ver": "d9b1f4d1a5aea91d101ae97a54714a5861af3f50",
"Node name for S&R": "WanVideoModelLoader"
},
"widgets_values": [
"WanVideo\\Wan2_1-FLF2V-14B-720P_fp8_e4m3fn.safetensors",
"fp16_fast",
"fp8_e4m3fn",
"offload_device",
"sageattn"
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 27,
"type": "WanVideoSampler",
@@ -1675,6 +1881,12 @@
"shape": 7,
"type": "MULTITALK_EMBEDS",
"link": null
},
{
"name": "freeinit_args",
"shape": 7,
"type": "FREEINITARGS",
"link": null
}
],
"outputs": [
@@ -1685,6 +1897,11 @@
"links": [
166
]
},
{
"name": "denoised_samples",
"type": "LATENT",
"links": null
}
],
"properties": {
@@ -1704,184 +1921,10 @@
1,
"",
"comfy",
""
]
},
{
"id": 106,
"type": "WanVideoLoraSelect",
"pos": [
-336.7720642089844,
-698.3348999023438
],
"size": [
424.9496765136719,
126
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "prev_lora",
"shape": 7,
"type": "WANVIDLORA",
"link": null
},
{
"name": "blocks",
"shape": 7,
"type": "SELECTEDBLOCKS",
"link": null
}
],
"outputs": [
{
"name": "lora",
"type": "WANVIDLORA",
"links": [
179
]
}
],
"properties": {
"cnr_id": "ComfyUI-WanVideoWrapper",
"ver": "974dd656dab305f7fa122cca435759105ea44488",
"Node name for S&R": "WanVideoLoraSelect"
},
"widgets_values": [
"Wan21_T2V_14B_lightx2v_cfg_step_distill_lora_rank32.safetensors",
1.2000000000000002,
0,
-1,
false
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 35,
"type": "WanVideoTorchCompileSettings",
"pos": [
-307.4797058105469,
-1197.4749755859375
],
"size": [
421.6000061035156,
202
],
"flags": {},
"order": 18,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "torch_compile_args",
"type": "WANCOMPILEARGS",
"slot_index": 0,
"links": [
190
]
}
],
"properties": {
"cnr_id": "ComfyUI-WanVideoWrapper",
"ver": "d9b1f4d1a5aea91d101ae97a54714a5861af3f50",
"Node name for S&R": "WanVideoTorchCompileSettings"
},
"widgets_values": [
"inductor",
false,
"default",
false,
64,
true,
128
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 22,
"type": "WanVideoModelLoader",
"pos": [
119.37029266357422,
-926.8419799804688
],
"size": [
477.4410095214844,
274
],
"flags": {},
"order": 24,
"mode": 0,
"inputs": [
{
"name": "compile_args",
"shape": 7,
"type": "WANCOMPILEARGS",
"link": 190
},
{
"name": "block_swap_args",
"shape": 7,
"type": "BLOCKSWAPARGS",
"link": 174
},
{
"name": "lora",
"shape": 7,
"type": "WANVIDLORA",
"link": 179
},
{
"name": "vram_management_args",
"shape": 7,
"type": "VRAM_MANAGEMENTARGS",
"link": null
},
{
"name": "vace_model",
"shape": 7,
"type": "VACEPATH",
"link": null
},
{
"name": "fantasytalking_model",
"shape": 7,
"type": "FANTASYTALKINGMODEL",
"link": null
},
{
"name": "multitalk_model",
"shape": 7,
"type": "MULTITALKMODEL",
"link": null
}
],
"outputs": [
{
"name": "model",
"type": "WANVIDEOMODEL",
"slot_index": 0,
"links": [
29,
103
]
}
],
"properties": {
"cnr_id": "ComfyUI-WanVideoWrapper",
"ver": "d9b1f4d1a5aea91d101ae97a54714a5861af3f50",
"Node name for S&R": "WanVideoModelLoader"
},
"widgets_values": [
"WanVideo\\Wan2_1-FLF2V-14B-720P_fp8_e4m3fn.safetensors",
"fp16_fast",
"fp8_e4m3fn",
"offload_device",
"sageattn"
],
"color": "#223",
"bgcolor": "#335"
]
}
],
"links": [
@@ -2216,13 +2259,13 @@
"config": {},
"extra": {
"ds": {
"scale": 0.6727499949326076,
"scale": 0.6115909044841886,
"offset": [
359.0502881120043,
1009.7385911003805
710.2610924809787,
967.8431584929548
]
},
"frontendVersion": "1.23.4",
"frontendVersion": "1.26.3",
"node_versions": {
"ComfyUI-WanVideoWrapper": "f8f423eceeadf2edcb58fab73701333e83ca733e",
"comfy-core": "0.3.26",
+78 -10
View File
@@ -1582,17 +1582,78 @@ class WanVideoScheduler: #WIP
def INPUT_TYPES(s):
return {"required": {
"scheduler": (scheduler_list, {"default": "unipc"}),
"steps": ("INT", {"default": 30, "min": 1, "tooltip": "Number of steps for the scheduler"}),
"shift": ("FLOAT", {"default": 5.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
"start_step": ("INT", {"default": 0, "min": 0, "tooltip": "Starting step for the scheduler"}),
"end_step": ("INT", {"default": -1, "min": -1, "tooltip": "Ending step for the scheduler"})
},
"optional": {
"sigmas": ("SIGMAS", ),
},
"hidden": {
"unique_id": "UNIQUE_ID",
},
}
RETURN_TYPES = (scheduler_list, )
RETURN_NAMES = ("scheduler",)
RETURN_TYPES = ("SIGMAS", "INT", "FLOAT", scheduler_list, "INT", "INT",)
RETURN_NAMES = ("sigmas", "steps", "shift", "scheduler", "start_step", "end_step")
FUNCTION = "process"
CATEGORY = "WanVideoWrapper"
EXPERIMENTAL = True
def process(self, scheduler):
return (scheduler,)
def process(self, scheduler, steps, start_step, end_step, shift, unique_id, sigmas=None):
sample_scheduler, timesteps = get_scheduler(
scheduler,
steps,
start_step, end_step, shift,
device,
sigmas=sigmas)
scheduler_dict = {
"sample_scheduler": sample_scheduler,
"timesteps": timesteps,
}
try:
from server import PromptServer
import io
import base64
import matplotlib.pyplot as plt
except:
PromptServer = None
if unique_id and PromptServer is not None:
try:
# Plot sigmas and save to a buffer
sigmas_np = sample_scheduler.full_sigmas[:-1].cpu().numpy()
buf = io.BytesIO()
fig = plt.figure(facecolor='#353535')
ax = fig.add_subplot(111)
ax.set_facecolor('#353535') # Set axes background color
ax.plot(sigmas_np)
ax.set_title("Sigmas", color='white') # Title font color
ax.set_xlabel("Step", color='white') # X label font color
ax.set_ylabel("Sigma Value", color='white') # Y label font color
ax.tick_params(axis='x', colors='white') # X tick color
ax.tick_params(axis='y', colors='white') # Y tick color
# Add split point if end_step is defined
if end_step != -1 and 0 <= end_step < len(sigmas_np):
ax.axvline(end_step, color='red', linestyle='--', linewidth=2, label='end_step split')
ax.legend()
plt.tight_layout()
plt.savefig(buf, format='png')
plt.close(fig)
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
buf.close()
# Send as HTML img tag with base64 data
html_img = f"<img src='data:image/png;base64,{img_base64}' alt='Sigmas Plot' style='max-width:100%; height:100%; overflow:hidden; display:block;'>"
PromptServer.instance.send_progress_text(html_img, unique_id)
except Exception as e:
print("Failed to send sigmas plot:", e)
pass
return (sigmas, steps, shift, scheduler_dict, start_step, end_step)
rope_functions = ["default", "comfy", "comfy_chunked"]
class WanVideoRoPEFunction:
@@ -1744,8 +1805,11 @@ class WanVideoSampler:
#region Scheduler
sample_scheduler = None
if scheduler != "multitalk":
sample_scheduler, timesteps, scheduler_step_args = get_scheduler(scheduler, steps, start_step, end_step, shift, device, transformer.dim, flowedit_args, denoise_strength, sigmas=sigmas, seed_g=seed_g)
if isinstance(scheduler, dict):
sample_scheduler = scheduler["sample_scheduler"]
timesteps = scheduler["timesteps"]
elif scheduler != "multitalk":
sample_scheduler, timesteps = get_scheduler(scheduler, steps, start_step, end_step, shift, device, transformer.dim, flowedit_args, denoise_strength, sigmas=sigmas)
log.info(f"sigmas: {sample_scheduler.sigmas}")
else:
timesteps = torch.tensor([1000, 750, 500, 250], device=device)
@@ -1761,7 +1825,11 @@ class WanVideoSampler:
start_step = steps - int(steps * denoise_strength) - 1
add_noise_to_samples = True #for now to not break old workflows
noise_pred_flipped = None
scheduler_step_args = {"generator": seed_g}
step_sig = inspect.signature(sample_scheduler.step)
for arg in list(scheduler_step_args.keys()):
if arg not in step_sig.parameters:
scheduler_step_args.pop(arg)
if isinstance(cfg, list):
if steps < len(cfg):
@@ -1778,7 +1846,7 @@ class WanVideoSampler:
vace_data = vace_context = vace_scale = None
fun_or_fl2v_model = has_ref = drop_last = False
phantom_latents = fun_ref_image = ATI_tracks = None
add_cond = attn_cond = attn_cond_neg = None
add_cond = attn_cond = attn_cond_neg = noise_pred_flipped = None
#I2V
image_cond = image_embeds.get("image_embeds", None)
@@ -2792,7 +2860,7 @@ class WanVideoSampler:
# FreeInit noise reinitialization (after first iteration)
if freeinit_args is not None and iter_idx > 0:
# restart scheduler for each iteration
sample_scheduler, timesteps, scheduler_step_args = get_scheduler(scheduler, steps, start_step, end_step, shift, device, transformer.dim, flowedit_args, denoise_strength, sigmas=sigmas, seed_g=seed_g)
sample_scheduler, timesteps = get_scheduler(scheduler, steps, start_step, end_step, shift, device, transformer.dim, flowedit_args, denoise_strength, sigmas=sigmas)
# Re-apply start_step and end_step logic to timesteps and sigmas
if end_step != -1:
@@ -3310,7 +3378,7 @@ class WanVideoSampler:
timesteps = [torch.tensor([t], device=device) for t in timesteps]
timesteps = [timestep_transform(t, shift=shift, num_timesteps=1000) for t in timesteps]
else:
sample_scheduler, timesteps, scheduler_step_args = get_scheduler(scheduler, total_steps, start_step, end_step, shift, device, transformer.dim, flowedit_args, denoise_strength, sigmas=sigmas, seed_g=seed_g)
sample_scheduler, timesteps = get_scheduler(scheduler, total_steps, start_step, end_step, shift, device, transformer.dim, flowedit_args, denoise_strength, sigmas=sigmas)
timesteps = [torch.tensor([float(t)], device=device) for t in timesteps] + [torch.tensor([0.], device=device)]
# sample videos
+3 -9
View File
@@ -23,7 +23,7 @@ scheduler_list = [
"multitalk"
]
def get_scheduler(scheduler, steps, start_step, end_step, shift, device, transformer_dim, flowedit_args, denoise_strength, sigmas=None, seed_g=None):
def get_scheduler(scheduler, steps, start_step, end_step, shift, device, transformer_dim=5120, flowedit_args=None, denoise_strength=1.0, sigmas=None):
timesteps = None
if 'unipc' in scheduler:
sample_scheduler = FlowUniPCMultistepScheduler(shift=shift)
@@ -130,6 +130,7 @@ def get_scheduler(scheduler, steps, start_step, end_step, shift, device, transfo
# Slice timesteps and sigmas once, based on indices
timesteps = timesteps[start_idx:end_idx+1]
sample_scheduler.full_sigmas = sample_scheduler.sigmas.clone()
sample_scheduler.sigmas = sample_scheduler.sigmas[start_idx:start_idx+len(timesteps)+1] # always one longer
@@ -138,11 +139,4 @@ def get_scheduler(scheduler, steps, start_step, end_step, shift, device, transfo
if hasattr(sample_scheduler, 'timesteps'):
sample_scheduler.timesteps = timesteps
if seed_g is not None:
scheduler_step_args = {"generator": seed_g}
step_sig = inspect.signature(sample_scheduler.step)
for arg in list(scheduler_step_args.keys()):
if arg not in step_sig.parameters:
scheduler_step_args.pop(arg)
return sample_scheduler, timesteps, scheduler_step_args
return sample_scheduler, timesteps