diff --git a/Sample_Workflows/Warped Framepack I2V or T2V Workflow.json b/Sample_Workflows/Warped Framepack I2V or T2V Workflow.json index 74f29fc..169c46c 100644 --- a/Sample_Workflows/Warped Framepack I2V or T2V Workflow.json +++ b/Sample_Workflows/Warped Framepack I2V or T2V Workflow.json @@ -1,122 +1,9 @@ { "id": "0a716eea-b098-4879-aa7f-a05fe3e5f252", "revision": 0, - "last_node_id": 185, - "last_link_id": 364, + "last_node_id": 188, + "last_link_id": 375, "nodes": [ - { - "id": 122, - "type": "WarpedImageScaleToSide", - "pos": [ - -106.85946655273438, - 1699.8187255859375 - ], - "size": [ - 286.3389892578125, - 146 - ], - "flags": { - "collapsed": true - }, - "order": 14, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 197 - } - ], - "outputs": [ - { - "name": "image", - "type": "IMAGE", - "links": [ - 198, - 210, - 359 - ] - }, - { - "name": "width", - "type": "INT", - "links": null - }, - { - "name": "height", - "type": "INT", - "links": null - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "1f5f5dd1352060b4db60431726812f980926bac4", - "Node name for S&R": "WarpedImageScaleToSide", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - 704, - "long_side", - false - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 156, - "type": "PreviewTextNode", - "pos": [ - 1469.10009765625, - 115.43976593017578 - ], - "size": [ - 568.0390014648438, - 162.57798767089844 - ], - "flags": {}, - "order": 31, - "mode": 0, - "inputs": [ - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": null - }, - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 299 - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": null - } - ], - "properties": { - "cnr_id": "comfyui_custom_nodes_alekpet", - "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", - "Node name for S&R": "PreviewTextNode", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "", - "Images Generated: 309", - [ - false, - true - ] - ] - }, { "id": 160, "type": "WarpedImageScaleToSide", @@ -218,7 +105,7 @@ "name": "IMAGE", "type": "IMAGE", "links": [ - 308 + 375 ] }, { @@ -254,8 +141,8 @@ "id": 126, "type": "PreviewTextNode", "pos": [ - -577.0264282226562, - 1630.280029296875 + -195.0296173095703, + 1953.715087890625 ], "size": [ 373.7130126953125, @@ -268,9 +155,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 203 } ], @@ -292,7 +176,6 @@ }, "widgets_values": [ "", - "1girl, photo, solo, towel, brown eyes, naked towel, brown hair, bed, photoshop, long hair, cover, text focus, real life, magazine cover, spread legs, nude, legs, lips, looking at viewer, realistic, bed sheet, naked sheet, retro artstyle, thighs, scan, chinese text, bath, english text, red hair, darkskinned female, thirdparty edit, wet, sitting, jpeg artifacts, blanket, tan, dark skin, bare shoulders, lingerie, 1980s (style), bare legs, watermark, wavy hair, hand on own knee, korean text, child, censored, paint.net, no panties, underwear, cover page, seethrough, knees, plump, pillow, bangs, breasts, nude cover, nipples, bathing, skirt, freckles, nose, dress, real life insert, topless, magazine (object), swimsuit, ass, bottomless, curly hair, speech bubble, strapless, character name, smile, no legwear, lying, screencap, on bed, original, polka dot, copyright name, mole, onsen, film grain, arm support, capcom, messy hair, logo, showering, pussy, blonde hair, disney, panties, artist name, reclining, cosplay, covering, brown theme, tanlines", [ false, true @@ -303,8 +186,8 @@ "id": 125, "type": "Joytag", "pos": [ - -571.378662109375, - 1533.4376220703125 + -189.3818817138672, + 1856.8726806640625 ], "size": [ 315, @@ -343,8 +226,8 @@ "id": 109, "type": "Int To Number (mtb)", "pos": [ - -93.01136016845703, - 1815.8675537109375 + 257.04156494140625, + 2145.9580078125 ], "size": [ 210, @@ -357,9 +240,6 @@ { "name": "int", "type": "INT", - "widget": { - "name": "int" - }, "link": 169 } ], @@ -378,16 +258,14 @@ "Node name for S&R": "Int To Number (mtb)", "widget_ue_connectable": {} }, - "widgets_values": [ - 0 - ] + "widgets_values": [] }, { "id": 111, "type": "Number to String", "pos": [ - -90.06990051269531, - 1928.3250732421875 + 259.9830017089844, + 2258.41552734375 ], "size": [ 210, @@ -424,8 +302,8 @@ "id": 110, "type": "Int To Number (mtb)", "pos": [ - -93.12239837646484, - 2014.6080322265625 + 256.9305114746094, + 2344.698486328125 ], "size": [ 210, @@ -438,9 +316,6 @@ { "name": "int", "type": "INT", - "widget": { - "name": "int" - }, "link": 170 } ], @@ -459,16 +334,14 @@ "Node name for S&R": "Int To Number (mtb)", "widget_ue_connectable": {} }, - "widgets_values": [ - 0 - ] + "widgets_values": [] }, { "id": 112, "type": "Number to String", "pos": [ - -91.51190185546875, - 2135.610595703125 + 258.5409851074219, + 2465.701171875 ], "size": [ 210, @@ -505,8 +378,8 @@ "id": 113, "type": "PreviewTextNode", "pos": [ - 183.2908935546875, - 1824.5853271484375 + 533.3433227539062, + 2154.67578125 ], "size": [ 210, @@ -519,9 +392,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 173 } ], @@ -541,54 +411,6 @@ }, "widgets_values": [ "", - "544", - [ - false, - true - ] - ] - }, - { - "id": 114, - "type": "PreviewTextNode", - "pos": [ - 189.06285095214844, - 1999.8714599609375 - ], - "size": [ - 210, - 112 - ], - "flags": {}, - "order": 27, - "mode": 0, - "inputs": [ - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 174 - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": null - } - ], - "title": "Height", - "properties": { - "cnr_id": "comfyui_custom_nodes_alekpet", - "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", - "Node name for S&R": "PreviewTextNode", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "", - "704", [ false, true @@ -599,8 +421,8 @@ "id": 116, "type": "PreviewImage", "pos": [ - 1101.5556640625, - 1841.376220703125 + 1221.3472900390625, + 1842.707275390625 ], "size": [ 383.0299987792969, @@ -630,8 +452,8 @@ "id": 128, "type": "PreviewImage", "pos": [ - 1512.965087890625, - 1835.5845947265625 + 1632.7567138671875, + 1836.9156494140625 ], "size": [ 418.9670104980469, @@ -661,8 +483,8 @@ "id": 96, "type": "PreviewImage", "pos": [ - 719.0128173828125, - 1845.8167724609375 + 838.80224609375, + 1847.1478271484375 ], "size": [ 363, @@ -743,7 +565,7 @@ ], "size": [ 321.2120056152344, - 178 + 202 ], "flags": { "pinned": true @@ -780,13 +602,13 @@ 358 ], "flags": {}, - "order": 32, + "order": 31, "mode": 0, "inputs": [ { "name": "images", "type": "IMAGE", - "link": 321 + "link": 365 }, { "name": "audio", @@ -805,14 +627,6 @@ "shape": 7, "type": "VAE", "link": null - }, - { - "name": "frame_rate", - "type": "FLOAT,INT", - "widget": { - "name": "frame_rate" - }, - "link": 322 } ], "outputs": [ @@ -902,8 +716,8 @@ "id": 62, "type": "FramePackFindNearestBucket", "pos": [ - 100.03911590576172, - 1499.2103271484375 + 450.0915832519531, + 1829.300048828125 ], "size": [ 320, @@ -943,9 +757,10 @@ ], "title": "Target width", "properties": { - "aux_id": "kijai/ComfyUI-FramePackWrapper", + "cnr_id": "comfyui-framepackwrapper_plusone", "ver": "4f9030a9f4c0bd67d86adf3d3dc07e37118c40bd", - "Node name for S&R": "FramePackFindNearestBucket" + "Node name for S&R": "FramePackFindNearestBucket", + "aux_id": "kijai/ComfyUI-FramePackWrapper" }, "widgets_values": [ 640 @@ -957,8 +772,8 @@ "id": 182, "type": "ImageResizeKJv2", "pos": [ - -97.16889190673828, - 1760.4610595703125 + 252.88401794433594, + 2090.55126953125 ], "size": [ 315, @@ -998,7 +813,7 @@ "type": "IMAGE", "links": [ 357, - 358 + 374 ] }, { @@ -1030,129 +845,6 @@ "color": "#223", "bgcolor": "#335" }, - { - "id": 159, - "type": "WarpedFramepackSampler", - "pos": [ - 715.7555541992188, - -54.92832946777344 - ], - "size": [ - 729.5650024414062, - 1316.168701171875 - ], - "flags": {}, - "order": 30, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "FramePackMODEL", - "link": 324 - }, - { - "name": "vae", - "type": "VAE", - "link": 328 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 363 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 364 - }, - { - "name": "clip_vision_model", - "type": "CLIP_VISION", - "link": 325 - }, - { - "name": "start_image", - "shape": 7, - "type": "IMAGE", - "link": 358 - }, - { - "name": "end_image", - "shape": 7, - "type": "IMAGE", - "link": 308 - }, - { - "name": "video_image_batch", - "shape": 7, - "type": "IMAGE", - "link": null - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "links": [ - 298, - 321 - ] - }, - { - "name": "generation_status", - "type": "STRING", - "links": [ - 299 - ] - }, - { - "name": "fps", - "type": "FLOAT", - "links": [ - 322 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "1f5f5dd1352060b4db60431726812f980926bac4", - "Node name for S&R": "WarpedFramepackSampler", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - 25, - true, - 0.25, - 1, - 12, - 24, - 148904751514746, - "randomize", - 61, - "next_lowest", - 10, - 12, - "unipc_bh1", - 256, - 64, - 64, - 8, - 0, - "disabled", - 1, - 30, - 1, - 1, - 1, - 704, - 544, - 5, - false - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 75, "type": "StringFunction|pysssss", @@ -1234,91 +926,6 @@ "color": "#4b4b5c", "bgcolor": "#373748" }, - { - "id": 8, - "type": "LoadImage", - "pos": [ - 2101.050537109375, - 961.3381958007812 - ], - "size": [ - 456.5150146484375, - 673.52001953125 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "label": " ", - "name": "IMAGE", - "type": "IMAGE", - "slot_index": 0, - "links": [ - 134 - ] - }, - { - "label": " ", - "name": "MASK", - "type": "MASK", - "links": null - } - ], - "title": "Start image", - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.18", - "Node name for S&R": "LoadImage" - }, - "widgets_values": [ - "s-l1600 (1).jpg", - "image" - ], - "color": "#432", - "bgcolor": "#653", - "shape": 1 - }, - { - "id": 94, - "type": "Reroute", - "pos": [ - -67.61992645263672, - 1523.0308837890625 - ], - "size": [ - 75, - 26 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "", - "type": "*", - "link": 134 - } - ], - "outputs": [ - { - "name": "", - "type": "IMAGE", - "links": [ - 108, - 109, - 116, - 197, - 202 - ] - } - ], - "properties": { - "showOutputText": false, - "horizontal": false - } - }, { "id": 177, "type": "Text Multiline", @@ -1331,7 +938,7 @@ 103.10320281982422 ], "flags": {}, - "order": 4, + "order": 3, "mode": 0, "inputs": [], "outputs": [ @@ -1361,62 +968,6 @@ "color": "#322", "bgcolor": "#533" }, - { - "id": 169, - "type": "WarpedLoadFramePackModel", - "pos": [ - 255.51377868652344, - 639.2796020507812 - ], - "size": [ - 340.20001220703125, - 174 - ], - "flags": { - "collapsed": true - }, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "compile_args", - "shape": 7, - "type": "FRAMEPACKCOMPILEARGS", - "link": null - }, - { - "name": "lora", - "shape": 7, - "type": "FPLORA", - "link": 323 - } - ], - "outputs": [ - { - "name": "model", - "type": "FramePackMODEL", - "links": [ - 324 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedLoadFramePackModel", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "FramePack_F1_I2V_HY_20250503_fp8_e4m3fn.safetensors", - "bf16", - "fp8_e4m3fn", - "offload_device", - "sdpa" - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 170, "type": "WarpedCLIPVisionLoader", @@ -1431,15 +982,15 @@ "flags": { "collapsed": true }, - "order": 5, + "order": 4, "mode": 0, "inputs": [], "outputs": [ { - "name": "CLIP_VISION", + "name": "clip_vision", "type": "CLIP_VISION", "links": [ - 325 + 373 ] } ], @@ -1456,45 +1007,6 @@ "color": "#323", "bgcolor": "#535" }, - { - "id": 172, - "type": "WarpedVAELoader", - "pos": [ - 265.1935119628906, - 761.4888305664062 - ], - "size": [ - 315, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "VAE", - "type": "VAE", - "links": [ - 328 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedVAELoader", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors" - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 184, "type": "WarpedDualEncoder", @@ -1529,22 +1041,22 @@ "name": "pos_conditioning", "type": "CONDITIONING", "links": [ - 363 + 371 ] }, { "name": "neg_conditioning", "type": "CONDITIONING", "links": [ - 364 + 372 ] } ], "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedDualEncoder" + "Node name for S&R": "WarpedDualEncoder", + "widget_ue_connectable": {} }, "widgets_values": [ "", @@ -1575,7 +1087,7 @@ "flags": { "collapsed": true }, - "order": 7, + "order": 5, "mode": 0, "inputs": [], "outputs": [ @@ -1590,8 +1102,8 @@ "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedDualCLIPLoader" + "Node name for S&R": "WarpedDualCLIPLoader", + "widget_ue_connectable": {} }, "widgets_values": [ "clip_l.safetensors", @@ -1620,17 +1132,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, - "link": null - }, - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, "link": 360 } ], @@ -1650,7 +1151,6 @@ }, "widgets_values": [ "", - "Images Generated: 309", [ false, true @@ -1671,7 +1171,7 @@ 322 ], "flags": {}, - "order": 8, + "order": 6, "mode": 0, "inputs": [ { @@ -1714,6 +1214,572 @@ ], "color": "#233", "bgcolor": "#355" + }, + { + "id": 186, + "type": "WarpedFramepackSampler", + "pos": [ + 704.1400756835938, + 1.9391456842422485 + ], + "size": [ + 745.3386840820312, + 1352.115966796875 + ], + "flags": {}, + "order": 30, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "FramePackMODEL", + "link": 369 + }, + { + "name": "vae", + "type": "VAE", + "link": 370 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 371 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 372 + }, + { + "name": "clip_vision_model", + "type": "CLIP_VISION", + "link": 373 + }, + { + "name": "start_image", + "shape": 7, + "type": "IMAGE", + "link": 374 + }, + { + "name": "end_image", + "shape": 7, + "type": "IMAGE", + "link": 375 + }, + { + "name": "video_image_batch", + "shape": 7, + "type": "IMAGE", + "link": null + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 365 + ] + }, + { + "name": "generation_status", + "type": "STRING", + "links": [ + 366 + ] + }, + { + "name": "seed", + "type": "INT", + "links": [ + 367 + ] + } + ], + "properties": { + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", + "Node name for S&R": "WarpedFramepackSampler" + }, + "widgets_values": [ + 25, + "use_magcache", + 0.25, + 0.125, + 0.225, + 3, + 1, + 12, + 24, + 0, + "randomize", + 321, + 65, + "next_lowest", + 12, + "unipc_bh1", + 256, + 64, + 64, + 8, + 0, + "disabled", + 1.0000000000000002, + 1, + 1, + 1, + 4, + 480, + 720, + 33, + true, + true, + "use_magcache", + "all", + 5, + false + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 8, + "type": "LoadImage", + "pos": [ + -703.3693237304688, + 1859.7655029296875 + ], + "size": [ + 456.5150146484375, + 673.52001953125 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [ + { + "label": " ", + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 134 + ] + }, + { + "label": " ", + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "title": "Start image", + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.18", + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "s-l1600 (1).jpg", + "image" + ], + "color": "#432", + "bgcolor": "#653", + "shape": 1 + }, + { + "id": 156, + "type": "PreviewTextNode", + "pos": [ + 1469.10009765625, + 115.43976593017578 + ], + "size": [ + 502.82000732421875, + 177.218994140625 + ], + "flags": {}, + "order": 32, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 366 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "", + [ + false, + true + ] + ] + }, + { + "id": 188, + "type": "WarpedNumericalConversion", + "pos": [ + 2004.5252685546875, + 119.06705474853516 + ], + "size": [ + 230.00741577148438, + 86 + ], + "flags": {}, + "order": 33, + "mode": 0, + "inputs": [ + { + "name": "int_value", + "shape": 7, + "type": "INT", + "link": 367 + }, + { + "name": "float_value", + "shape": 7, + "type": "FLOAT", + "link": null + }, + { + "name": "number_value", + "shape": 7, + "type": "NUMBER", + "link": null + } + ], + "outputs": [ + { + "name": "int", + "type": "INT", + "links": null + }, + { + "name": "float", + "type": "FLOAT", + "links": null + }, + { + "name": "number", + "type": "NUMBER", + "links": null + }, + { + "name": "string", + "type": "STRING", + "links": [ + 368 + ] + } + ], + "properties": { + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", + "Node name for S&R": "WarpedNumericalConversion" + }, + "widgets_values": [], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 187, + "type": "PreviewTextNode", + "pos": [ + 2286.010009765625, + 119.10883331298828 + ], + "size": [ + 210, + 118.65499114990234 + ], + "flags": {}, + "order": 34, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 368 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "", + [ + false, + true + ] + ] + }, + { + "id": 94, + "type": "Reroute", + "pos": [ + -166.11412048339844, + 1670.77294921875 + ], + "size": [ + 75, + 26 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "", + "type": "*", + "link": 134 + } + ], + "outputs": [ + { + "name": "", + "type": "IMAGE", + "links": [ + 108, + 109, + 116, + 197, + 202 + ] + } + ], + "properties": { + "showOutputText": false, + "horizontal": false + } + }, + { + "id": 122, + "type": "WarpedImageScaleToSide", + "pos": [ + 243.1934356689453, + 2029.908447265625 + ], + "size": [ + 286.3389892578125, + 146 + ], + "flags": { + "collapsed": true + }, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 197 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 198, + 210, + 359 + ] + }, + { + "name": "width", + "type": "INT", + "links": null + }, + { + "name": "height", + "type": "INT", + "links": null + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "1f5f5dd1352060b4db60431726812f980926bac4", + "Node name for S&R": "WarpedImageScaleToSide", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + 768, + "long_side", + false + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 114, + "type": "PreviewTextNode", + "pos": [ + 535.122314453125, + 2332.6240234375 + ], + "size": [ + 210, + 112 + ], + "flags": {}, + "order": 27, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 174 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "title": "Height", + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "", + [ + false, + true + ] + ] + }, + { + "id": 169, + "type": "WarpedLoadFramePackModel", + "pos": [ + 255.51377868652344, + 639.2796020507812 + ], + "size": [ + 340.20001220703125, + 174 + ], + "flags": { + "collapsed": true + }, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "compile_args", + "shape": 7, + "type": "FRAMEPACKCOMPILEARGS", + "link": null + }, + { + "name": "lora", + "shape": 7, + "type": "FPLORA", + "link": 323 + } + ], + "outputs": [ + { + "name": "model", + "type": "FramePackMODEL", + "links": [ + 369 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedLoadFramePackModel", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "FramePack_F1_I2V_HY_20250503_fp8_e4m3fn.safetensors", + "bf16", + "fp8_e4m3fn", + "cpu", + "sdpa" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 172, + "type": "WarpedVAELoader", + "pos": [ + 259.8695373535156, + 754.8338623046875 + ], + "size": [ + 315, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 8, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "vae", + "type": "VAE", + "links": [ + 370 + ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedVAELoader", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "hunyuan_video_vae_bf16.safetensors" + ], + "color": "#323", + "bgcolor": "#535" } ], "links": [ @@ -1837,14 +1903,6 @@ 1, "STRING" ], - [ - 299, - 159, - 1, - 156, - 1, - "STRING" - ], [ 306, 86, @@ -1861,14 +1919,6 @@ 0, "IMAGE" ], - [ - 308, - 162, - 0, - 159, - 6, - "IMAGE" - ], [ 309, 62, @@ -1885,22 +1935,6 @@ 2, "INT" ], - [ - 321, - 159, - 0, - 167, - 0, - "IMAGE" - ], - [ - 322, - 159, - 2, - 167, - 4, - "FLOAT,INT" - ], [ 323, 158, @@ -1909,30 +1943,6 @@ 1, "FPLORA" ], - [ - 324, - 169, - 0, - 159, - 0, - "FramePackMODEL" - ], - [ - 325, - 170, - 0, - 159, - 4, - "CLIP_VISION" - ], - [ - 328, - 172, - 0, - 159, - 1, - "VAE" - ], [ 355, 62, @@ -1957,14 +1967,6 @@ 0, "IMAGE" ], - [ - 358, - 182, - 0, - 159, - 5, - "IMAGE" - ], [ 359, 122, @@ -1978,7 +1980,7 @@ 75, 0, 183, - 1, + 0, "STRING" ], [ @@ -1998,20 +2000,92 @@ "CLIP" ], [ - 363, + 365, + 186, + 0, + 167, + 0, + "IMAGE" + ], + [ + 366, + 186, + 1, + 156, + 0, + "STRING" + ], + [ + 367, + 186, + 2, + 188, + 0, + "INT" + ], + [ + 368, + 188, + 3, + 187, + 0, + "STRING" + ], + [ + 369, + 169, + 0, + 186, + 0, + "FramePackMODEL" + ], + [ + 370, + 172, + 0, + 186, + 1, + "VAE" + ], + [ + 371, 184, 0, - 159, + 186, 2, "CONDITIONING" ], [ - 364, + 372, 184, 1, - 159, + 186, 3, "CONDITIONING" + ], + [ + 373, + 170, + 0, + 186, + 4, + "CLIP_VISION" + ], + [ + 374, + 182, + 0, + 186, + 5, + "IMAGE" + ], + [ + 375, + 162, + 0, + 186, + 6, + "IMAGE" ] ], "groups": [ @@ -2021,8 +2095,8 @@ "bounding": [ 209.15011596679688, 357.3632507324219, - 1852.821044921875, - 1013.7479858398438 + 1870.1240234375, + 1363.801025390625 ], "color": "#3f789e", "font_size": 24, @@ -2030,10 +2104,10 @@ }, { "id": 4, - "title": "Internals", + "title": "Image Rescale", "bounding": [ - -145.03280639648438, - 1402.1607666015625, + 205.02011108398438, + 1732.25048828125, 615.3648071289062, 812.6204223632812 ], @@ -2045,8 +2119,8 @@ "id": 7, "title": "Joytag", "bounding": [ - -589.95458984375, - 1410.5030517578125, + -207.95777893066406, + 1733.9381103515625, 404.8689880371094, 640.3510131835938 ], @@ -2058,8 +2132,8 @@ "id": 8, "title": "Preview Scaled Input", "bounding": [ - 707.7703247070312, - 1729.945068359375, + 827.5597534179688, + 1731.276123046875, 1247.3919677734375, 541.8569946289062 ], @@ -2079,6 +2153,19 @@ "color": "#3f789e", "font_size": 24, "flags": {} + }, + { + "id": 10, + "title": "I2V", + "bounding": [ + -718.7009887695312, + 1732.24169921875, + 498.03900146484375, + 850.6489868164062 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} } ], "config": {}, @@ -2086,13 +2173,14 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - -90.54899999999859, - -246.84300000000115 + 172.98900000000114, + -153.6730000000042 ] }, + "frontendVersion": "1.20.5", "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "VHS_MetadataImage": true, "VHS_KeepIntermediate": true, "links_added_by_ue": [] diff --git a/Sample_Workflows/Warped Framepack V2V Workflow.json b/Sample_Workflows/Warped Framepack V2V Workflow.json index 50a0265..134bc26 100644 --- a/Sample_Workflows/Warped Framepack V2V Workflow.json +++ b/Sample_Workflows/Warped Framepack V2V Workflow.json @@ -1,153 +1,9 @@ { "id": "0a716eea-b098-4879-aa7f-a05fe3e5f252", "revision": 0, - "last_node_id": 246, - "last_link_id": 597, + "last_node_id": 251, + "last_link_id": 609, "nodes": [ - { - "id": 15, - "type": "PrimitiveNode", - "pos": [ - 250, - 450 - ], - "size": [ - 450, - 140 - ], - "flags": { - "pinned": true - }, - "order": 0, - "mode": 0, - "inputs": [], - "outputs": [ - { - "label": " ", - "name": "STRING", - "type": "STRING", - "widget": { - "name": "text" - }, - "slot_index": 0, - "links": [ - 19 - ] - } - ], - "title": "Negative prompt", - "properties": { - "Run widget replace on values": false - }, - "color": "#322", - "bgcolor": "#533", - "shape": 1 - }, - { - "id": 206, - "type": "VHS_VideoCombine", - "pos": [ - 1532.7237548828125, - 327.9851379394531 - ], - "size": [ - 658.8189697265625, - 358 - ], - "flags": { - "collapsed": false - }, - "order": 31, - "mode": 0, - "inputs": [ - { - "label": " ", - "localized_name": "images", - "name": "images", - "type": "IMAGE", - "link": 546 - }, - { - "label": " ", - "localized_name": "audio", - "name": "audio", - "shape": 7, - "type": "AUDIO", - "link": null - }, - { - "label": " ", - "localized_name": "meta_batch", - "name": "meta_batch", - "shape": 7, - "type": "VHS_BatchManager", - "link": null - }, - { - "label": " ", - "localized_name": "vae", - "name": "vae", - "shape": 7, - "type": "VAE", - "link": null - }, - { - "name": "frame_rate", - "type": "FLOAT,INT", - "widget": { - "name": "frame_rate" - }, - "link": 583 - } - ], - "outputs": [ - { - "label": " ", - "localized_name": "Filenames", - "name": "Filenames", - "type": "VHS_FILENAMES", - "slot_index": 0, - "links": null - } - ], - "title": "Original Video Clip", - "properties": { - "cnr_id": "comfyui-videohelpersuite", - "ver": "4c7858ddd5126f7293dc3c9f6e0fc4c263cde079", - "Node name for S&R": "VHS_VideoCombine", - "aux_id": "Kosinkadink/ComfyUI-VideoHelperSuite", - "widget_ue_connectable": {} - }, - "widgets_values": { - "frame_rate": 30, - "loop_count": 0, - "filename_prefix": "Test/%date:yyyy_MM_dd%/lowres/vid", - "format": "video/h264-mp4", - "pix_fmt": "yuv420p", - "crf": 19, - "save_metadata": false, - "trim_to_audio": false, - "pingpong": false, - "save_output": false, - "save_images": false, - "videopreview": { - "hidden": false, - "paused": false, - "params": { - "filename": "vid_00001.mp4", - "subfolder": "Test\\2025_05_23\\lowres", - "type": "temp", - "format": "video/h264-mp4", - "frame_rate": 30, - "workflow": "", - "fullpath": "C:\\Users\\Bobby\\AppData\\Local\\Temp\\latentsync_4b33fc84\\Test\\2025_05_23\\lowres\\vid_00001.mp4" - } - } - }, - "color": "#233", - "bgcolor": "#355", - "shape": 1 - }, { "id": 42, "type": "VHS_VideoCombine", @@ -167,14 +23,12 @@ "inputs": [ { "label": " ", - "localized_name": "images", "name": "images", "type": "IMAGE", - "link": 519 + "link": 599 }, { "label": " ", - "localized_name": "audio", "name": "audio", "shape": 7, "type": "AUDIO", @@ -182,7 +36,6 @@ }, { "label": " ", - "localized_name": "meta_batch", "name": "meta_batch", "shape": 7, "type": "VHS_BatchManager", @@ -190,7 +43,6 @@ }, { "label": " ", - "localized_name": "vae", "name": "vae", "shape": 7, "type": "VAE", @@ -198,7 +50,7 @@ }, { "name": "frame_rate", - "type": "FLOAT,INT", + "type": "FLOAT", "widget": { "name": "frame_rate" }, @@ -208,7 +60,6 @@ "outputs": [ { "label": " ", - "localized_name": "Filenames", "name": "Filenames", "type": "VHS_FILENAMES", "slot_index": 0, @@ -264,37 +115,17 @@ 147.08999633789062 ], "flags": {}, - "order": 30, + "order": 29, "mode": 0, "inputs": [ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, - "link": null - }, - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 528 - }, - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": null + "link": 607 } ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": null @@ -308,19 +139,20 @@ }, "widgets_values": [ "", - "WarpedSamplerCustomAdv: Processing Interrupted.\nImages Generated: 1", [ false, true ] - ] + ], + "color": "#322", + "bgcolor": "#533" }, { "id": 62, "type": "FramePackFindNearestBucket", "pos": [ - 255.86570739746094, - 1451.06787109375 + 270, + 1710 ], "size": [ 320, @@ -329,11 +161,10 @@ "flags": { "collapsed": true }, - "order": 16, + "order": 15, "mode": 0, "inputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "link": 321 @@ -341,7 +172,6 @@ ], "outputs": [ { - "localized_name": "width", "name": "width", "type": "INT", "links": [ @@ -353,7 +183,6 @@ ] }, { - "localized_name": "height", "name": "height", "type": "INT", "links": [ @@ -367,9 +196,10 @@ ], "title": "Target width", "properties": { - "aux_id": "kijai/ComfyUI-FramePackWrapper", + "cnr_id": "comfyui-framepackwrapper_plusone", "ver": "4f9030a9f4c0bd67d86adf3d3dc07e37118c40bd", - "Node name for S&R": "FramePackFindNearestBucket" + "Node name for S&R": "FramePackFindNearestBucket", + "aux_id": "kijai/ComfyUI-FramePackWrapper" }, "widgets_values": [ 640 @@ -381,8 +211,8 @@ "id": 172, "type": "WarpedImageScaleToSide", "pos": [ - 236.25662231445312, - 1647.8043212890625 + 250, + 1910 ], "size": [ 327.5999755859375, @@ -391,11 +221,10 @@ "flags": { "collapsed": true }, - "order": 9, + "order": 7, "mode": 0, "inputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "link": 545 @@ -403,7 +232,6 @@ ], "outputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "links": [ @@ -411,13 +239,11 @@ ] }, { - "localized_name": "width", "name": "width", "type": "INT", "links": null }, { - "localized_name": "height", "name": "height", "type": "INT", "links": null @@ -442,8 +268,8 @@ "id": 218, "type": "ImageResizeKJv2", "pos": [ - 244.24554443359375, - 1732.8939208984375 + 260, + 2000 ], "size": [ 315, @@ -452,11 +278,10 @@ "flags": { "collapsed": true }, - "order": 21, + "order": 20, "mode": 0, "inputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "link": 489 @@ -480,7 +305,6 @@ ], "outputs": [ { - "localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": [ @@ -492,13 +316,11 @@ ] }, { - "localized_name": "width", "name": "width", "type": "INT", "links": [] }, { - "localized_name": "height", "name": "height", "type": "INT", "links": [] @@ -526,29 +348,25 @@ "id": 109, "type": "Int To Number (mtb)", "pos": [ - 249.8813018798828, - 1822.491455078125 + 270, + 2090 ], "size": [ 210, 58 ], "flags": {}, - "order": 18, + "order": 17, "mode": 0, "inputs": [ { "name": "int", "type": "INT", - "widget": { - "name": "int" - }, "link": 169 } ], "outputs": [ { - "localized_name": "NUMBER", "name": "NUMBER", "type": "NUMBER", "links": [ @@ -562,27 +380,24 @@ "Node name for S&R": "Int To Number (mtb)", "widget_ue_connectable": {} }, - "widgets_values": [ - 0 - ] + "widgets_values": [] }, { "id": 111, "type": "Number to String", "pos": [ - 247.0994415283203, - 1935.0830078125 + 260, + 2200 ], "size": [ 210, 26 ], "flags": {}, - "order": 24, + "order": 23, "mode": 0, "inputs": [ { - "localized_name": "number", "name": "number", "type": "NUMBER", "link": 171 @@ -590,7 +405,6 @@ ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [ @@ -610,29 +424,25 @@ "id": 110, "type": "Int To Number (mtb)", "pos": [ - 255.7595672607422, - 2012.848388671875 + 270, + 2280 ], "size": [ 210, 58 ], "flags": {}, - "order": 19, + "order": 18, "mode": 0, "inputs": [ { "name": "int", "type": "INT", - "widget": { - "name": "int" - }, "link": 170 } ], "outputs": [ { - "localized_name": "NUMBER", "name": "NUMBER", "type": "NUMBER", "links": [ @@ -646,77 +456,31 @@ "Node name for S&R": "Int To Number (mtb)", "widget_ue_connectable": {} }, - "widgets_values": [ - 0 - ] - }, - { - "id": 112, - "type": "Number to String", - "pos": [ - 251.513916015625, - 2122.80517578125 - ], - "size": [ - 210, - 26 - ], - "flags": {}, - "order": 25, - "mode": 0, - "inputs": [ - { - "localized_name": "number", - "name": "number", - "type": "NUMBER", - "link": 172 - } - ], - "outputs": [ - { - "localized_name": "STRING", - "name": "STRING", - "type": "STRING", - "links": [ - 174 - ] - } - ], - "properties": { - "cnr_id": "pr-was-node-suite-comfyui-47064894", - "ver": "056badacda52e88d29d6a65f9509cd3115ace0f2", - "Node name for S&R": "Number to String", - "widget_ue_connectable": {} - }, "widgets_values": [] }, { "id": 113, "type": "PreviewTextNode", "pos": [ - 492.7752685546875, - 1827.6163330078125 + 510, + 2090 ], "size": [ 210, 112 ], "flags": {}, - "order": 28, + "order": 27, "mode": 0, "inputs": [ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 173 } ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": null @@ -731,7 +495,6 @@ }, "widgets_values": [ "", - "832", [ false, true @@ -742,29 +505,25 @@ "id": 114, "type": "PreviewTextNode", "pos": [ - 491.62591552734375, - 1991.3267822265625 + 510, + 2250 ], "size": [ 210, 112 ], "flags": {}, - "order": 29, + "order": 28, "mode": 0, "inputs": [ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 174 } ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": null @@ -779,7 +538,6 @@ }, "widgets_values": [ "", - "480", [ false, true @@ -798,11 +556,10 @@ 377.3840026855469 ], "flags": {}, - "order": 26, + "order": 25, "mode": 0, "inputs": [ { - "localized_name": "images", "name": "images", "type": "IMAGE", "link": 487 @@ -830,11 +587,10 @@ 386.88409423828125 ], "flags": {}, - "order": 11, + "order": 10, "mode": 0, "inputs": [ { - "localized_name": "images", "name": "images", "type": "IMAGE", "link": 116 @@ -861,11 +617,10 @@ 379.1000061035156 ], "flags": {}, - "order": 15, + "order": 14, "mode": 0, "inputs": [ { - "localized_name": "images", "name": "images", "type": "IMAGE", "link": 320 @@ -885,19 +640,18 @@ "id": 125, "type": "Joytag", "pos": [ - -268.7087097167969, - 1980.9879150390625 + 193.1479949951172, + 2589.25634765625 ], "size": [ 315, 58 ], "flags": {}, - "order": 12, + "order": 11, "mode": 2, "inputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "link": 202 @@ -905,7 +659,6 @@ ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [ @@ -927,29 +680,25 @@ "id": 126, "type": "PreviewTextNode", "pos": [ - -258.69073486328125, - 2088.900146484375 + 203.16600036621094, + 2697.168701171875 ], "size": [ 373.7130126953125, 335.6080017089844 ], "flags": {}, - "order": 14, + "order": 13, "mode": 2, "inputs": [ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 203 } ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [ @@ -966,7 +715,6 @@ }, "widgets_values": [ "", - "1girl, oral, photo, fellatio, bestiality, penis, nipples, breasts, uncensored, tanlines, tattoo, tan, animal, dog, hetero, nude, solo focus, makeup, spread legs, :>=, long hair, large breasts, medium breasts, squatting, jewelry, eyeshadow, closed eyes, bracelet, lipstick, black hair, sex, cum, 1boy, jpeg artifacts, pussy, plant, bikini tan, testicles, breasts apart, arm tattoo, indoors, lips, horse, censored, crotch, arm support, completely nude, thighs, huge penis, knees, piercing, watermark, all fours, potted plant, wolf, photoshop, large areolae, day, handjob, fingers, fingernails, erection, eyeliner, bodypaint, legs, creature, ass, cum in mouth, brown hair, outdoors, pubic hair, sagging breasts, necklace, monkey, floor, legs apart, hanging breasts, original, nipple piercing, bed, wavy hair, saliva, mosaic censoring, bathroom, large penis, mascara, kneeling, nail polish, feet out of frame, bent over, solo, navel, white background, out of frame, real life, open mouth, standing, vaginal, leg tattoo, eyelashes", [ false, true @@ -985,7 +733,7 @@ 130 ], "flags": {}, - "order": 1, + "order": 0, "mode": 0, "inputs": [], "outputs": [ @@ -1017,12 +765,11 @@ 273.9331970214844 ], "flags": {}, - "order": 2, + "order": 1, "mode": 0, "inputs": [], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [ @@ -1059,21 +806,17 @@ 394.5712890625 ], "flags": {}, - "order": 22, + "order": 21, "mode": 0, "inputs": [ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 562 } ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": null @@ -1088,7 +831,6 @@ }, "widgets_values": [ "", - "breast physics, erotic, a female is giving a man a handjob. she is very seductive, seductive lips, (((large breasts:1.3))), erect nipples. bikini, solo focus, makeup, short hair, jewelry, eyeshadow, bracelet, lipstick, platinum blonde hair, pussy, breasts, bikini tan, lips, thighs, ass, piercing, large areolae, day, fingernails, nail_polish, eyeliner, legs, necklace, mascara, pussy, penis, WetPen, erection, (((cum:1.3))), (((cumshot:1.3))), (((ejaculation:1.3))), breasts, nipples, nail polish, navel, real life, eyelashes", [ false, true @@ -1101,8 +843,8 @@ "id": 171, "type": "WarpedImageScaleToSide", "pos": [ - 238.07754516601562, - 1519.7978515625 + 260, + 1780 ], "size": [ 327.5999755859375, @@ -1111,11 +853,10 @@ "flags": { "collapsed": true }, - "order": 13, + "order": 12, "mode": 0, "inputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "link": 544 @@ -1123,7 +864,6 @@ ], "outputs": [ { - "localized_name": "image", "name": "image", "type": "IMAGE", "links": [ @@ -1134,13 +874,11 @@ ] }, { - "localized_name": "width", "name": "width", "type": "INT", "links": null }, { - "localized_name": "height", "name": "height", "type": "INT", "links": null @@ -1161,349 +899,19 @@ "color": "#323", "bgcolor": "#535" }, - { - "id": 217, - "type": "ImageResizeKJv2", - "pos": [ - 255.3506317138672, - 1589.2437744140625 - ], - "size": [ - 315, - 242 - ], - "flags": { - "collapsed": true - }, - "order": 20, - "mode": 0, - "inputs": [ - { - "localized_name": "image", - "name": "image", - "type": "IMAGE", - "link": 482 - }, - { - "name": "width", - "type": "INT", - "widget": { - "name": "width" - }, - "link": 483 - }, - { - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": 484 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 487 - ] - }, - { - "localized_name": "width", - "name": "width", - "type": "INT", - "links": [] - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "links": [] - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "1.1.0", - "Node name for S&R": "ImageResizeKJv2", - "widget_ue_connectable": {} - }, - "widgets_values": [ - 512, - 512, - "lanczos", - "crop", - "0, 0, 0", - "top", - 2 - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 240, - "type": "WarpedCLIPVisionLoader", - "pos": [ - 263.1093444824219, - 655.1119995117188 - ], - "size": [ - 315, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "CLIP_VISION", - "name": "CLIP_VISION", - "type": "CLIP_VISION", - "links": [ - 585 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedCLIPVisionLoader", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "sigclip_vision_patch14_384.safetensors" - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 226, - "type": "WarpedFramepackSampler", - "pos": [ - 705.669677734375, - -161.8272705078125 - ], - "size": [ - 812.206787109375, - 1317.0423583984375 - ], - "flags": {}, - "order": 27, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "FramePackMODEL", - "link": 587 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 592 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 596 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 597 - }, - { - "localized_name": "clip_vision_model", - "name": "clip_vision_model", - "type": "CLIP_VISION", - "link": 585 - }, - { - "localized_name": "start_image", - "name": "start_image", - "shape": 7, - "type": "IMAGE", - "link": null - }, - { - "localized_name": "end_image", - "name": "end_image", - "shape": 7, - "type": "IMAGE", - "link": null - }, - { - "localized_name": "video_image_batch", - "name": "video_image_batch", - "shape": 7, - "type": "IMAGE", - "link": 526 - }, - { - "name": "fps", - "shape": 7, - "type": "FLOAT", - "widget": { - "name": "fps" - }, - "link": null - }, - { - "name": "fps", - "shape": 7, - "type": "FLOAT", - "widget": { - "name": "fps" - }, - "link": 547 - } - ], - "outputs": [ - { - "localized_name": "images", - "name": "images", - "type": "IMAGE", - "links": [ - 519 - ] - }, - { - "localized_name": "generation_status", - "name": "generation_status", - "type": "STRING", - "links": [ - 528 - ] - }, - { - "localized_name": "fps", - "name": "fps", - "type": "FLOAT", - "links": [ - 583, - 584 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "1f5f5dd1352060b4db60431726812f980926bac4", - "Node name for S&R": "WarpedFramepackSampler", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - 25, - true, - 0.25000000000000006, - 1, - 12.000000000000002, - 0, - 150177627030164, - "randomize", - 61, - "next_lowest", - 10, - 12, - "unipc_bh1", - 256, - 64, - 64, - 8, - 0, - "disabled", - 0.7500000000000001, - 30, - 1, - 1, - 1, - 640, - 640, - 5, - false - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 223, - "type": "WarpedFramepackMultiLoraSelect", - "pos": [ - 230.29212951660156, - -272.5264892578125 - ], - "size": [ - 428.4000244140625, - 322 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "prev_lora", - "name": "prev_lora", - "shape": 7, - "type": "FPLORA", - "link": null - } - ], - "outputs": [ - { - "localized_name": "lora", - "name": "lora", - "type": "FPLORA", - "links": [ - 508, - 586 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "1f5f5dd1352060b4db60431726812f980926bac4", - "Node name for S&R": "WarpedFramepackMultiLoraSelect", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "None", - 1.0000000000000002, - false, - "None", - 1.0000000000000002, - false, - "None", - 1.0000000000000002, - false, - "None", - 1.0000000000000002, - false - ], - "color": "#233", - "bgcolor": "#355" - }, { "id": 94, "type": "Reroute", "pos": [ - 444.41595458984375, - 1433.385498046875 + 460, + 1700 ], "size": [ 75, 26 ], "flags": {}, - "order": 10, + "order": 8, "mode": 0, "inputs": [ { @@ -1529,231 +937,6 @@ "horizontal": false } }, - { - "id": 229, - "type": "WarpedBundleVideoImages", - "pos": [ - 237.0240478515625, - 1129.588134765625 - ], - "size": [ - 374.3047790527344, - 250 - ], - "flags": { - "collapsed": false - }, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "image_batch", - "name": "image_batch", - "type": "IMAGE", - "links": [ - 545, - 546 - ] - }, - { - "localized_name": "first_image", - "name": "first_image", - "type": "IMAGE", - "links": [ - 543 - ] - }, - { - "localized_name": "last_image", - "name": "last_image", - "type": "IMAGE", - "links": null - }, - { - "localized_name": "num_frames", - "name": "num_frames", - "type": "INT", - "links": null - }, - { - "localized_name": "width", - "name": "width", - "type": "INT", - "links": null - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "links": null - }, - { - "localized_name": "fps", - "name": "fps", - "type": "FLOAT", - "links": [ - 547 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "1f5f5dd1352060b4db60431726812f980926bac4", - "Node name for S&R": "WarpedBundleVideoImages", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "h:\\fortest\\Brooklynthj1c.mp4", - 0, - 301, - true - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 243, - "type": "WarpedVAELoader", - "pos": [ - 265.771240234375, - 708.3519897460938 - ], - "size": [ - 315, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 592 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedVAELoader", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors" - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 241, - "type": "WarpedLoadFramePackModel", - "pos": [ - 265.7712097167969, - 760.2609252929688 - ], - "size": [ - 340.20001220703125, - 174 - ], - "flags": { - "collapsed": true - }, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "compile_args", - "name": "compile_args", - "shape": 7, - "type": "FRAMEPACKCOMPILEARGS", - "link": null - }, - { - "localized_name": "lora", - "name": "lora", - "shape": 7, - "type": "FPLORA", - "link": 586 - } - ], - "outputs": [ - { - "localized_name": "model", - "name": "model", - "type": "FramePackMODEL", - "links": [ - 587 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedLoadFramePackModel", - "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", - "widget_ue_connectable": {} - }, - "widgets_values": [ - "FramePack_F1_I2V_HY_20250503_fp8_e4m3fn.safetensors", - "bf16", - "fp8_e4m3fn", - "offload_device", - "sdpa" - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 244, - "type": "WarpedDualCLIPLoader", - "pos": [ - 270.2339172363281, - 820.504638671875 - ], - "size": [ - 315, - 130 - ], - "flags": { - "collapsed": true - }, - "order": 7, - "mode": 0, - "inputs": [], - "outputs": [ - { - "localized_name": "clip", - "name": "clip", - "type": "CLIP", - "links": [ - 593 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedDualCLIPLoader" - }, - "widgets_values": [ - "clip_l.safetensors", - "Kijai\\llava_llama3_fp8_scaled.safetensors", - "hunyuan_video", - "cuda" - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 75, "type": "StringFunction|pysssss", @@ -1768,7 +951,7 @@ "flags": { "collapsed": true }, - "order": 17, + "order": 16, "mode": 0, "inputs": [ { @@ -1792,7 +975,6 @@ ], "outputs": [ { - "localized_name": "STRING", "name": "STRING", "type": "STRING", "slot_index": 0, @@ -1834,22 +1016,552 @@ "bgcolor": "#373748" }, { - "id": 245, - "type": "WarpedDualEncoder", + "id": 206, + "type": "VHS_VideoCombine", "pos": [ - 260.91717529296875, - 881.7305908203125 + 1532.7237548828125, + 327.9851379394531 ], "size": [ - 396.0069885253906, - 145.4290008544922 + 658.8189697265625, + 358 ], + "flags": { + "collapsed": false + }, + "order": 33, + "mode": 0, + "inputs": [ + { + "label": " ", + "name": "images", + "type": "IMAGE", + "link": 600 + }, + { + "label": " ", + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": null + }, + { + "label": " ", + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "label": " ", + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + }, + { + "name": "frame_rate", + "type": "FLOAT", + "widget": { + "name": "frame_rate" + }, + "link": 583 + } + ], + "outputs": [ + { + "label": " ", + "name": "Filenames", + "type": "VHS_FILENAMES", + "slot_index": 0, + "links": null + } + ], + "title": "Original Video Clip", + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "4c7858ddd5126f7293dc3c9f6e0fc4c263cde079", + "Node name for S&R": "VHS_VideoCombine", + "aux_id": "Kosinkadink/ComfyUI-VideoHelperSuite", + "widget_ue_connectable": {} + }, + "widgets_values": { + "frame_rate": 30, + "loop_count": 0, + "filename_prefix": "Test/%date:yyyy_MM_dd%/lowres/vid", + "format": "video/h264-mp4", + "pix_fmt": "yuv420p", + "crf": 19, + "save_metadata": false, + "trim_to_audio": false, + "pingpong": false, + "save_output": false, + "save_images": false, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "vid_00001.mp4", + "subfolder": "Test\\2025_05_23\\lowres", + "type": "temp", + "format": "video/h264-mp4", + "frame_rate": 30, + "workflow": "", + "fullpath": "C:\\Users\\Bobby\\AppData\\Local\\Temp\\latentsync_4b33fc84\\Test\\2025_05_23\\lowres\\vid_00001.mp4" + } + } + }, + "color": "#233", + "bgcolor": "#355", + "shape": 1 + }, + { + "id": 217, + "type": "ImageResizeKJv2", + "pos": [ + 270, + 1850 + ], + "size": [ + 315, + 242 + ], + "flags": { + "collapsed": true + }, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 482 + }, + { + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 483 + }, + { + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 484 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 487, + 606 + ] + }, + { + "name": "width", + "type": "INT", + "links": [] + }, + { + "name": "height", + "type": "INT", + "links": [] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "1.1.0", + "Node name for S&R": "ImageResizeKJv2", + "widget_ue_connectable": {} + }, + "widgets_values": [ + 512, + 512, + "lanczos", + "crop", + "0, 0, 0", + "top", + 2 + ], + "color": "#223", + "bgcolor": "#335" + }, + { + "id": 250, + "type": "PreviewTextNode", + "pos": [ + 2372.084716796875, + 99.85952758789062 + ], + "size": [ + 272.1940002441406, + 108.49099731445312 + ], "flags": {}, - "order": 23, + "order": 31, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 609 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "", + [ + false, + true + ] + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 247, + "type": "WarpedFramepackSampler", + "pos": [ + 670.8655395507812, + 301.4139709472656 + ], + "size": [ + 849.1566772460938, + 1438.6309814453125 + ], + "flags": {}, + "order": 26, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "FramePackMODEL", + "link": 601 + }, + { + "name": "vae", + "type": "VAE", + "link": 602 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 603 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 604 + }, + { + "name": "clip_vision_model", + "type": "CLIP_VISION", + "link": 605 + }, + { + "name": "start_image", + "shape": 7, + "type": "IMAGE", + "link": 606 + }, + { + "name": "end_image", + "shape": 7, + "type": "IMAGE", + "link": null + }, + { + "name": "video_image_batch", + "shape": 7, + "type": "IMAGE", + "link": null + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 599 + ] + }, + { + "name": "generation_status", + "type": "STRING", + "links": [ + 607 + ] + }, + { + "name": "seed", + "type": "INT", + "links": [ + 608 + ] + } + ], + "properties": { + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", + "Node name for S&R": "WarpedFramepackSampler" + }, + "widgets_values": [ + 25, + "use_magcache", + 0.25, + 0.125, + 0.225, + 3, + 1, + 12, + 24, + 0, + "randomize", + 301, + 61, + "next_lowest", + 12, + "unipc_bh1", + 256, + 64, + 64, + 8, + 0, + "disabled", + 1.2, + 1, + 1, + 1, + 5, + 480, + 720, + 17, + false, + false, + "use_magcache", + "last", + 5, + false + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 251, + "type": "WarpedNumericalConversion", + "pos": [ + 2064.55615234375, + 116.40503692626953 + ], + "size": [ + 230.00741577148438, + 86 + ], + "flags": {}, + "order": 30, + "mode": 0, + "inputs": [ + { + "name": "int_value", + "shape": 7, + "type": "INT", + "link": 608 + }, + { + "name": "float_value", + "shape": 7, + "type": "FLOAT", + "link": null + }, + { + "name": "number_value", + "shape": 7, + "type": "NUMBER", + "link": null + } + ], + "outputs": [ + { + "name": "int", + "type": "INT", + "links": null + }, + { + "name": "float", + "type": "FLOAT", + "links": null + }, + { + "name": "number", + "type": "NUMBER", + "links": null + }, + { + "name": "string", + "type": "STRING", + "links": [ + 609 + ] + } + ], + "properties": { + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", + "Node name for S&R": "WarpedNumericalConversion" + }, + "widgets_values": [], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 112, + "type": "Number to String", + "pos": [ + 270, + 2390 + ], + "size": [ + 210, + 26 + ], + "flags": {}, + "order": 24, + "mode": 0, + "inputs": [ + { + "name": "number", + "type": "NUMBER", + "link": 172 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 174 + ] + } + ], + "properties": { + "cnr_id": "pr-was-node-suite-comfyui-47064894", + "ver": "056badacda52e88d29d6a65f9509cd3115ace0f2", + "Node name for S&R": "Number to String", + "widget_ue_connectable": {} + }, + "widgets_values": [] + }, + { + "id": 229, + "type": "WarpedBundleVideoImages", + "pos": [ + 235.69305419921875, + 1382.4801025390625 + ], + "size": [ + 374.3047790527344, + 250 + ], + "flags": { + "collapsed": false + }, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "image_batch", + "type": "IMAGE", + "links": [ + 545, + 600 + ] + }, + { + "name": "first_image", + "type": "IMAGE", + "links": [ + 543 + ] + }, + { + "name": "last_image", + "type": "IMAGE", + "links": null + }, + { + "name": "num_frames", + "type": "INT", + "links": null + }, + { + "name": "width", + "type": "INT", + "links": null + }, + { + "name": "height", + "type": "INT", + "links": null + }, + { + "name": "fps", + "type": "FLOAT", + "links": [ + 547 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "1f5f5dd1352060b4db60431726812f980926bac4", + "Node name for S&R": "WarpedBundleVideoImages", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "h:\\fortest\\Brooklynthj1c.mp4", + 0, + 301, + true + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 245, + "type": "WarpedDualEncoder", + "pos": [ + 247.60723876953125, + 1161.2418212890625 + ], + "size": [ + 396.0069885253906, + 162 + ], + "flags": {}, + "order": 22, "mode": 0, "inputs": [ { - "localized_name": "clip", "name": "clip", "type": "CLIP", "link": 593 @@ -1865,27 +1577,27 @@ ], "outputs": [ { - "localized_name": "pos_conditioning", "name": "pos_conditioning", "type": "CONDITIONING", "links": [ - 596 + 596, + 603 ] }, { - "localized_name": "neg_conditioning", "name": "neg_conditioning", "type": "CONDITIONING", "links": [ - 597 + 597, + 604 ] } ], "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedDualEncoder" + "Node name for S&R": "WarpedDualEncoder", + "widget_ue_connectable": {} }, "widgets_values": [ "", @@ -1901,6 +1613,245 @@ ], "color": "#323", "bgcolor": "#535" + }, + { + "id": 244, + "type": "WarpedDualCLIPLoader", + "pos": [ + 255.59298706054688, + 1098.685546875 + ], + "size": [ + 315, + 130 + ], + "flags": { + "collapsed": true + }, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "clip", + "type": "CLIP", + "links": [ + 593 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedDualCLIPLoader", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "clip_l.safetensors", + "Kijai\\llava_llama3_fp8_scaled.safetensors", + "hunyuan_video", + "cuda" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 243, + "type": "WarpedVAELoader", + "pos": [ + 260.447265625, + 1005.1642456054688 + ], + "size": [ + 315, + 70 + ], + "flags": { + "collapsed": true + }, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "vae", + "type": "VAE", + "links": [ + 592, + 602 + ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedVAELoader", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "hunyuan_video_vae_bf16.safetensors" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 240, + "type": "WarpedCLIPVisionLoader", + "pos": [ + 259.1163635253906, + 954.5867919921875 + ], + "size": [ + 315, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "clip_vision", + "type": "CLIP_VISION", + "links": [ + 585, + 605 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedCLIPVisionLoader", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "sigclip_vision_patch14_384.safetensors" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 223, + "type": "WarpedFramepackMultiLoraSelect", + "pos": [ + 227.630126953125, + 318.437255859375 + ], + "size": [ + 428.4000244140625, + 322 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "prev_lora", + "shape": 7, + "type": "FPLORA", + "link": null + } + ], + "outputs": [ + { + "name": "lora", + "type": "FPLORA", + "links": [ + 508, + 586 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "1f5f5dd1352060b4db60431726812f980926bac4", + "Node name for S&R": "WarpedFramepackMultiLoraSelect", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "None", + 1.0000000000000002, + false, + "None", + 1.0000000000000002, + false, + "None", + 1.0000000000000002, + false, + "None", + 1.0000000000000002, + false + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 241, + "type": "WarpedLoadFramePackModel", + "pos": [ + 260.4472351074219, + 1051.7498779296875 + ], + "size": [ + 340.20001220703125, + 174 + ], + "flags": { + "collapsed": true + }, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "compile_args", + "shape": 7, + "type": "FRAMEPACKCOMPILEARGS", + "link": null + }, + { + "name": "lora", + "shape": 7, + "type": "FPLORA", + "link": 586 + } + ], + "outputs": [ + { + "name": "model", + "type": "FramePackMODEL", + "links": [ + 587, + 601 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedLoadFramePackModel", + "aux_id": "WarpedAnimation/ComfyUI-WarpedToolset", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "FramePack_F1_I2V_HY_20250503_fp8_e4m3fn.safetensors", + "bf16", + "fp8_e4m3fn", + "cpu", + "sdpa" + ], + "color": "#323", + "bgcolor": "#535" } ], "links": [ @@ -2112,14 +2063,6 @@ 0, "IMAGE" ], - [ - 546, - 229, - 0, - 206, - 0, - "IMAGE" - ], [ 547, 229, @@ -2215,6 +2158,94 @@ 226, 3, "CONDITIONING" + ], + [ + 599, + 247, + 0, + 42, + 0, + "IMAGE" + ], + [ + 600, + 229, + 0, + 206, + 0, + "IMAGE" + ], + [ + 601, + 241, + 0, + 247, + 0, + "FramePackMODEL" + ], + [ + 602, + 243, + 0, + 247, + 1, + "VAE" + ], + [ + 603, + 245, + 0, + 247, + 2, + "CONDITIONING" + ], + [ + 604, + 245, + 1, + 247, + 3, + "CONDITIONING" + ], + [ + 605, + 240, + 0, + 247, + 4, + "CLIP_VISION" + ], + [ + 606, + 217, + 0, + 247, + 5, + "IMAGE" + ], + [ + 607, + 247, + 1, + 185, + 0, + "STRING" + ], + [ + 608, + 247, + 2, + 251, + 0, + "INT" + ], + [ + 609, + 251, + 3, + 250, + 0, + "STRING" ] ], "groups": [ @@ -2248,8 +2279,8 @@ "id": 6, "title": "Joytag", "bounding": [ - -369.85595703125, - 1901.4510498046875, + 92.00102996826172, + 2509.71923828125, 533.8428955078125, 567.5452880859375 ], @@ -2263,16 +2294,17 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - -178.39499999999808, - -174.96900000000775 + -13.48409999999581, + -445.16200000000214 ] }, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "VHS_MetadataImage": true, "VHS_KeepIntermediate": true, - "links_added_by_ue": [] + "links_added_by_ue": [], + "frontendVersion": "1.20.5" }, "version": 0.4 } \ No newline at end of file diff --git a/Sample_Workflows/Warped Hunyuan I2V Using I2V Model Workflow.json b/Sample_Workflows/Warped Hunyuan I2V Using I2V Model Workflow.json index f131923..abcfe65 100644 --- a/Sample_Workflows/Warped Hunyuan I2V Using I2V Model Workflow.json +++ b/Sample_Workflows/Warped Hunyuan I2V Using I2V Model Workflow.json @@ -1,8 +1,8 @@ { "id": "6fee7bc1-689d-4cd1-b4f9-7c20316a44f8", "revision": 0, - "last_node_id": 310, - "last_link_id": 1045, + "last_node_id": 311, + "last_link_id": 1047, "nodes": [ { "id": 152, @@ -22,9 +22,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 1032 } ], @@ -42,7 +39,6 @@ }, "widgets_values": [ "", - "****** WarpedSamplerCustomAdvLatent: Total Images Generated 97 ******", [ false, true @@ -82,42 +78,6 @@ "toggleRestriction": "default" } }, - { - "id": 145, - "type": "UnetLoaderGGUF", - "pos": [ - -390.6521911621094, - 488.76007080078125 - ], - "size": [ - 296.70855712890625, - 65.69999694824219 - ], - "flags": { - "collapsed": true - }, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "slot_index": 0, - "links": [ - 840 - ] - } - ], - "properties": { - "cnr_id": "comfyui-gguf", - "ver": "298192ed60f8ca821c6fe5f8030cae23424cada5", - "Node name for S&R": "UnetLoaderGGUF" - }, - "widgets_values": [ - "hunyuan-video-i2v-720p-Q5_K_M.gguf" - ] - }, { "id": 16, "type": "KSamplerSelect", @@ -132,13 +92,12 @@ "flags": { "collapsed": true }, - "order": 2, + "order": 1, "mode": 0, "inputs": [], "outputs": [ { "name": "SAMPLER", - "shape": 3, "type": "SAMPLER", "slot_index": 0, "links": [ @@ -168,7 +127,7 @@ 234 ], "flags": {}, - "order": 18, + "order": 17, "mode": 0, "inputs": [ { @@ -327,7 +286,7 @@ 26 ], "flags": {}, - "order": 11, + "order": 13, "mode": 0, "inputs": [ { @@ -344,7 +303,7 @@ "links": [ 526, 1014, - 1039 + 1046 ] } ], @@ -367,7 +326,7 @@ "flags": { "collapsed": true }, - "order": 14, + "order": 15, "mode": 0, "inputs": [ { @@ -410,7 +369,7 @@ 298.5 ], "flags": {}, - "order": 16, + "order": 14, "mode": 0, "inputs": [ { @@ -460,7 +419,7 @@ 346.54290771484375 ], "flags": {}, - "order": 8, + "order": 9, "mode": 0, "inputs": [ { @@ -499,53 +458,6 @@ "all" ] }, - { - "id": 280, - "type": "CLIPVisionEncode", - "pos": [ - -386.97509765625, - 593.4584350585938 - ], - "size": [ - 336.4000244140625, - 87.9000015258789 - ], - "flags": { - "collapsed": true - }, - "order": 13, - "mode": 0, - "inputs": [ - { - "name": "clip_vision", - "type": "CLIP_VISION", - "link": 1045 - }, - { - "name": "image", - "type": "IMAGE", - "link": 1036 - } - ], - "outputs": [ - { - "name": "CLIP_VISION_OUTPUT", - "type": "CLIP_VISION_OUTPUT", - "slot_index": 0, - "links": [ - 897 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.26", - "Node name for S&R": "CLIPVisionEncode" - }, - "widgets_values": [ - "none" - ] - }, { "id": 306, "type": "WarpedImageResize", @@ -555,10 +467,10 @@ ], "size": [ 315, - 190 + 214 ], "flags": {}, - "order": 9, + "order": 8, "mode": 0, "inputs": [ { @@ -606,100 +518,12 @@ 400, 720, "lanczos", - "center" + "center", + false ], "color": "#323", "bgcolor": "#535" }, - { - "id": 307, - "type": "WarpedTeaCache", - "pos": [ - -60.81998062133789, - 26.74999237060547 - ], - "size": [ - 315, - 106 - ], - "flags": { - "collapsed": true - }, - "order": 15, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 1039 - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "links": [ - 1040 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedTeaCache" - }, - "widgets_values": [ - "hunyuan_video", - 0.4, - 1 - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 67, - "type": "ModelSamplingSD3", - "pos": [ - -56.19779968261719, - 79.80845642089844 - ], - "size": [ - 210, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 17, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 1040 - } - ], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "slot_index": 0, - "links": [ - 1008 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.26", - "Node name for S&R": "ModelSamplingSD3" - }, - "widgets_values": [ - 12 - ], - "color": "#222", - "bgcolor": "#000" - }, { "id": 22, "type": "BasicGuider", @@ -731,7 +555,6 @@ "outputs": [ { "name": "GUIDER", - "shape": 3, "type": "GUIDER", "slot_index": 0, "links": [ @@ -761,7 +584,7 @@ 476.8260192871094 ], "flags": {}, - "order": 12, + "order": 11, "mode": 0, "inputs": [ { @@ -790,7 +613,7 @@ 314 ], "flags": {}, - "order": 3, + "order": 2, "mode": 0, "inputs": [], "outputs": [ @@ -832,7 +655,7 @@ 202 ], "flags": {}, - "order": 4, + "order": 3, "mode": 2, "inputs": [], "outputs": [ @@ -875,8 +698,8 @@ "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedBundleAllVideoImages" + "Node name for S&R": "WarpedBundleAllVideoImages", + "widget_ue_connectable": {} }, "widgets_values": [ "", @@ -1010,82 +833,94 @@ } }, { - "id": 309, - "type": "WarpedVAELoader", + "id": 67, + "type": "ModelSamplingSD3", "pos": [ - -379.2938232421875, - 542.3250732421875 + -56.19779968261719, + 79.80845642089844 ], "size": [ - 315, + 210, 58 ], "flags": { "collapsed": true }, - "order": 5, + "order": 18, "mode": 0, - "inputs": [], + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 1047 + } + ], "outputs": [ { - "name": "VAE", - "type": "VAE", + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, "links": [ - 1041, - 1042, - 1043 + 1008 ] } ], "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedVAELoader" + "cnr_id": "comfy-core", + "ver": "0.3.26", + "Node name for S&R": "ModelSamplingSD3" }, "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors" + 12 ], - "color": "#323", - "bgcolor": "#535" + "color": "#222", + "bgcolor": "#000" }, { - "id": 310, - "type": "WarpedCLIPVisionLoader", + "id": 311, + "type": "MagCache", "pos": [ - -388.6111145019531, - 659.4535522460938 + -51.868019104003906, + 28.558996200561523 ], "size": [ - 315, - 58 + 270, + 178 ], "flags": { "collapsed": true }, - "order": 6, + "order": 16, "mode": 0, - "inputs": [], + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 1046 + } + ], "outputs": [ { - "name": "CLIP_VISION", - "type": "CLIP_VISION", + "name": "model", + "type": "MODEL", "links": [ - 1045 + 1047 ] } ], "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedCLIPVisionLoader" + "aux_id": "Zehong-Ma/ComfyUI-MagCache", + "ver": "7d4e982bf7955498afca891c7094c48a70985537", + "Node name for S&R": "MagCache" }, "widgets_values": [ - "clip_vision_h_fp8_e4m3fn.safetensors" - ], - "color": "#323", - "bgcolor": "#535" + "hunyuan_video", + 0.12000000000000002, + 0.2, + 3, + 0, + -1 + ] }, { "id": 144, @@ -1101,7 +936,7 @@ "flags": { "collapsed": true }, - "order": 7, + "order": 4, "mode": 0, "inputs": [], "outputs": [ @@ -1124,6 +959,172 @@ "Kijai\\llava-llama-3-8B-v1_1-Q4_K_M.gguf", "hunyuan_video" ] + }, + { + "id": 145, + "type": "UnetLoaderGGUF", + "pos": [ + -390.6521911621094, + 488.76007080078125 + ], + "size": [ + 296.70855712890625, + 65.69999694824219 + ], + "flags": { + "collapsed": true + }, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 840 + ] + } + ], + "properties": { + "cnr_id": "comfyui-gguf", + "ver": "298192ed60f8ca821c6fe5f8030cae23424cada5", + "Node name for S&R": "UnetLoaderGGUF" + }, + "widgets_values": [ + "hunyuan-video-i2v-720p-Q5_K_M.gguf" + ] + }, + { + "id": 309, + "type": "WarpedVAELoader", + "pos": [ + -379.2938232421875, + 542.3250732421875 + ], + "size": [ + 315, + 70 + ], + "flags": { + "collapsed": true + }, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "vae", + "type": "VAE", + "links": [ + 1041, + 1042, + 1043 + ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedVAELoader", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "hunyuan_video_vae_bf16.safetensors" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 280, + "type": "CLIPVisionEncode", + "pos": [ + -386.97509765625, + 593.4584350585938 + ], + "size": [ + 336.4000244140625, + 87.9000015258789 + ], + "flags": { + "collapsed": true + }, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "clip_vision", + "type": "CLIP_VISION", + "link": 1045 + }, + { + "name": "image", + "type": "IMAGE", + "link": 1036 + } + ], + "outputs": [ + { + "name": "CLIP_VISION_OUTPUT", + "type": "CLIP_VISION_OUTPUT", + "slot_index": 0, + "links": [ + 897 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.26", + "Node name for S&R": "CLIPVisionEncode" + }, + "widgets_values": [ + "none" + ] + }, + { + "id": 310, + "type": "WarpedCLIPVisionLoader", + "pos": [ + -388.6111145019531, + 659.4535522460938 + ], + "size": [ + 315, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 7, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "clip_vision", + "type": "CLIP_VISION", + "links": [ + 1045 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedCLIPVisionLoader", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "clip_vision_h_fp8_e4m3fn.safetensors" + ], + "color": "#323", + "bgcolor": "#535" } ], "links": [ @@ -1287,22 +1288,6 @@ 4, "INT" ], - [ - 1039, - 81, - 0, - 307, - 0, - "MODEL" - ], - [ - 1040, - 307, - 0, - 67, - 0, - "MODEL" - ], [ 1041, 309, @@ -1334,6 +1319,22 @@ 280, 0, "CLIP_VISION" + ], + [ + 1046, + 81, + 0, + 311, + 0, + "MODEL" + ], + [ + 1047, + 311, + 0, + 67, + 0, + "MODEL" ] ], "groups": [ @@ -1356,14 +1357,14 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - 573.6200000000003, - 80.58299999999217 + 670.783000000001, + 269.58499999999214 ] }, "groupNodes": {}, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "node_versions": { "ComfyUI_Custom_Nodes_AlekPet": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", "ComfyUI-GGUF": "298192ed60f8ca821c6fe5f8030cae23424cada5", @@ -1374,7 +1375,8 @@ "Hunyuan-Multi-Lora-Loader": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce" }, "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true + "VHS_KeepIntermediate": true, + "frontendVersion": "1.20.5" }, "version": 0.4 } \ No newline at end of file diff --git a/Sample_Workflows/Warped Hunyuan I2V Workflow.json b/Sample_Workflows/Warped Hunyuan I2V Workflow.json index cd815e8..3078028 100644 --- a/Sample_Workflows/Warped Hunyuan I2V Workflow.json +++ b/Sample_Workflows/Warped Hunyuan I2V Workflow.json @@ -1,56 +1,9 @@ { "id": "dfed12ef-fd2b-4870-b037-155a07e06804", "revision": 0, - "last_node_id": 288, - "last_link_id": 921, + "last_node_id": 291, + "last_link_id": 925, "nodes": [ - { - "id": 152, - "type": "PreviewTextNode", - "pos": [ - 743.6790771484375, - -206.6033477783203 - ], - "size": [ - 342.3085632324219, - 114.48214721679688 - ], - "flags": {}, - "order": 25, - "mode": 0, - "inputs": [ - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 884 - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": null - } - ], - "properties": { - "cnr_id": "comfyui_custom_nodes_alekpet", - "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", - "Node name for S&R": "PreviewTextNode" - }, - "widgets_values": [ - "", - "****** WarpedSamplerCustomAdv: Total Images Generated 73 ******", - [ - false, - true - ] - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 99, "type": "Fast Groups Muter (rgthree)", @@ -113,7 +66,6 @@ "outputs": [ { "name": "GUIDER", - "shape": 3, "type": "GUIDER", "slot_index": 0, "links": [ @@ -357,7 +309,6 @@ "outputs": [ { "name": "SAMPLER", - "shape": 3, "type": "SAMPLER", "slot_index": 0, "links": [ @@ -477,57 +428,6 @@ "color": "#323", "bgcolor": "#535" }, - { - "id": 258, - "type": "HunyuanMultiLoraLoader", - "pos": [ - -425.3673400878906, - -16.010576248168945 - ], - "size": [ - 364.0858154296875, - 346.54290771484375 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 840 - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "slot_index": 0, - "links": [ - 888 - ] - } - ], - "properties": { - "cnr_id": "Hunyuan-Multi-Lora-Loader", - "ver": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce", - "Node name for S&R": "HunyuanMultiLoraLoader" - }, - "widgets_values": [ - "Hunyuan\\leapfusion_img2vid960x544_comfy.safetensors", - 1, - "all", - "Hunyuan\\RsmFunbags2Hy.safetensors", - 0.3, - "all", - "None", - 1, - "all", - "None", - 1, - "all" - ] - }, { "id": 284, "type": "WarpedCreateSpecialImageBatch", @@ -622,116 +522,6 @@ 0.8 ] }, - { - "id": 275, - "type": "WarpedSamplerCustomAdv", - "pos": [ - 354.22686767578125, - -38.07691192626953 - ], - "size": [ - 556.1101684570312, - 1426.0767822265625 - ], - "flags": {}, - "order": 23, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 912 - }, - { - "name": "vae", - "type": "VAE", - "link": 878 - }, - { - "name": "guider", - "type": "GUIDER", - "link": 879 - }, - { - "name": "sampler", - "type": "SAMPLER", - "link": 880 - }, - { - "name": "sigmas", - "type": "SIGMAS", - "link": 881 - }, - { - "name": "seed", - "type": "INT", - "widget": { - "name": "seed" - }, - "link": 882 - }, - { - "name": "scaling_strength", - "shape": 7, - "type": "FLOAT", - "widget": { - "name": "scaling_strength" - }, - "link": 891 - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "slot_index": 0, - "links": [ - 883 - ] - }, - { - "name": "latents", - "type": "LATENT", - "links": null - }, - { - "name": "seed", - "type": "INT", - "links": null - }, - { - "name": "generation_status", - "type": "STRING", - "slot_index": 3, - "links": [ - 884 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedSamplerCustomAdv" - }, - "widgets_values": [ - 364003112445620, - "randomize", - 256, - 64, - 64, - 8, - 256, - 64, - 64, - 8, - 0, - 1, - 1, - false - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 190, "type": "VHS_VideoCombine", @@ -820,7 +610,7 @@ ], "size": [ 315, - 190 + 214 ], "flags": {}, "order": 7, @@ -867,7 +657,8 @@ 544, 960, "lanczos", - "center" + "center", + false ], "color": "#323", "bgcolor": "#535" @@ -908,95 +699,6 @@ "horizontal": false } }, - { - "id": 286, - "type": "WarpedTeaCache", - "pos": [ - -30.570003509521484, - 12.229994773864746 - ], - "size": [ - 315, - 106 - ], - "flags": { - "collapsed": true - }, - "order": 20, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 917 - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "links": [ - 918 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedTeaCache" - }, - "widgets_values": [ - "hunyuan_video", - 0.4, - 1 - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 67, - "type": "ModelSamplingSD3", - "pos": [ - -41.097835540771484, - 72.95838928222656 - ], - "size": [ - 210, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 21, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 918 - } - ], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "slot_index": 0, - "links": [ - 195 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.27", - "Node name for S&R": "ModelSamplingSD3" - }, - "widgets_values": [ - 12 - ], - "color": "#222", - "bgcolor": "#000" - }, { "id": 26, "type": "FluxGuidance", @@ -1023,7 +725,6 @@ "outputs": [ { "name": "CONDITIONING", - "shape": 3, "type": "CONDITIONING", "slot_index": 0, "links": [ @@ -1107,7 +808,7 @@ "links": [ 526, 893, - 917 + 922 ] } ], @@ -1135,19 +836,24 @@ "inputs": [], "outputs": [ { - "name": "VAE", + "name": "vae", "type": "VAE", "links": [ 919, 920 ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null } ], "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedVAELoader" + "Node name for S&R": "WarpedVAELoader", + "widget_ue_connectable": {} }, "widgets_values": [ "hunyuan_video_vae_bf16.safetensors" @@ -1221,7 +927,7 @@ "inputs": [], "outputs": [ { - "name": "CLIP_VISION", + "name": "clip_vision", "type": "CLIP_VISION", "links": [ 921 @@ -1231,14 +937,419 @@ "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedCLIPVisionLoader" + "Node name for S&R": "WarpedCLIPVisionLoader", + "widget_ue_connectable": {} }, "widgets_values": [ "clip_vision_h_fp8_e4m3fn.safetensors" ], "color": "#323", "bgcolor": "#535" + }, + { + "id": 67, + "type": "ModelSamplingSD3", + "pos": [ + -41.097835540771484, + 72.95838928222656 + ], + "size": [ + 210, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 21, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 923 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 195 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.27", + "Node name for S&R": "ModelSamplingSD3" + }, + "widgets_values": [ + 12 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 289, + "type": "MagCache", + "pos": [ + -34.564998626708984, + 25.896995544433594 + ], + "size": [ + 270, + 178 + ], + "flags": { + "collapsed": true + }, + "order": 20, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 922 + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "links": [ + 923 + ] + } + ], + "properties": { + "aux_id": "Zehong-Ma/ComfyUI-MagCache", + "ver": "7d4e982bf7955498afca891c7094c48a70985537", + "Node name for S&R": "MagCache" + }, + "widgets_values": [ + "hunyuan_video", + 0.12000000000000002, + 0.2, + 3, + 0, + -1 + ] + }, + { + "id": 258, + "type": "HunyuanMultiLoraLoader", + "pos": [ + -425.3673400878906, + -16.010576248168945 + ], + "size": [ + 364.0858154296875, + 346.54290771484375 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 840 + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "slot_index": 0, + "links": [ + 888 + ] + } + ], + "properties": { + "cnr_id": "Hunyuan-Multi-Lora-Loader", + "ver": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce", + "Node name for S&R": "HunyuanMultiLoraLoader" + }, + "widgets_values": [ + "Hunyuan\\leapfusion_img2vid960x544_comfy.safetensors", + 1, + "all", + "RsmHy\\RsmFunbagsHy.safetensors", + 0.3, + "all", + "None", + 1, + "all", + "None", + 1, + "all" + ] + }, + { + "id": 152, + "type": "PreviewTextNode", + "pos": [ + 738.3551025390625, + -239.87826538085938 + ], + "size": [ + 342.3085632324219, + 114.48214721679688 + ], + "flags": {}, + "order": 26, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 884 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode" + }, + "widgets_values": [ + "", + [ + false, + true + ] + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 290, + "type": "PreviewTextNode", + "pos": [ + 1363.6016845703125, + -237.5402374267578 + ], + "size": [ + 239.82156372070312, + 103.83414459228516 + ], + "flags": {}, + "order": 27, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 925 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode" + }, + "widgets_values": [ + "", + [ + false, + true + ] + ], + "color": "#322", + "bgcolor": "#533" + }, + { + "id": 275, + "type": "WarpedSamplerCustomAdv", + "pos": [ + 354.22686767578125, + -38.07691192626953 + ], + "size": [ + 556.1101684570312, + 1426.0767822265625 + ], + "flags": {}, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 912 + }, + { + "name": "vae", + "type": "VAE", + "link": 878 + }, + { + "name": "guider", + "type": "GUIDER", + "link": 879 + }, + { + "name": "sampler", + "type": "SAMPLER", + "link": 880 + }, + { + "name": "sigmas", + "type": "SIGMAS", + "link": 881 + }, + { + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": 882 + }, + { + "name": "scaling_strength", + "shape": 7, + "type": "FLOAT", + "widget": { + "name": "scaling_strength" + }, + "link": 891 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 883 + ] + }, + { + "name": "latents", + "type": "LATENT", + "links": null + }, + { + "name": "seed", + "type": "INT", + "links": [ + 924 + ] + }, + { + "name": "generation_status", + "type": "STRING", + "slot_index": 3, + "links": [ + 884 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedSamplerCustomAdv" + }, + "widgets_values": [ + 364003112445620, + "randomize", + 256, + 64, + 64, + 8, + 256, + 64, + 64, + 8, + 0, + 1, + 1, + false + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 291, + "type": "WarpedNumericalConversion", + "pos": [ + 1106.1021728515625, + -226.99310302734375 + ], + "size": [ + 230.00741577148438, + 86 + ], + "flags": {}, + "order": 25, + "mode": 0, + "inputs": [ + { + "name": "int_value", + "shape": 7, + "type": "INT", + "link": 924 + }, + { + "name": "float_value", + "shape": 7, + "type": "FLOAT", + "link": null + }, + { + "name": "number_value", + "shape": 7, + "type": "NUMBER", + "link": null + } + ], + "outputs": [ + { + "name": "int", + "type": "INT", + "links": null + }, + { + "name": "float", + "type": "FLOAT", + "links": null + }, + { + "name": "number", + "type": "NUMBER", + "links": null + }, + { + "name": "string", + "type": "STRING", + "links": [ + 925 + ] + } + ], + "properties": { + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", + "Node name for S&R": "WarpedNumericalConversion" + }, + "color": "#323", + "bgcolor": "#535" } ], "links": [ @@ -1442,22 +1553,6 @@ 0, "IMAGE" ], - [ - 917, - 81, - 0, - 286, - 0, - "MODEL" - ], - [ - 918, - 286, - 0, - 67, - 0, - "MODEL" - ], [ 919, 287, @@ -1481,6 +1576,38 @@ 280, 0, "CLIP_VISION" + ], + [ + 922, + 81, + 0, + 289, + 0, + "MODEL" + ], + [ + 923, + 289, + 0, + 67, + 0, + "MODEL" + ], + [ + 924, + 275, + 2, + 291, + 0, + "INT" + ], + [ + 925, + 291, + 3, + 290, + 0, + "STRING" ] ], "groups": [ @@ -1503,14 +1630,14 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - 531.0279999999998, - -244.1810000000078 + 608.2260000000005, + -152.3420000000077 ] }, "groupNodes": {}, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "node_versions": { "ComfyUI_Custom_Nodes_AlekPet": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", "comfy-core": "0.3.26", @@ -1521,7 +1648,8 @@ "comfyui-videohelpersuite": "1.5.2" }, "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true + "VHS_KeepIntermediate": true, + "frontendVersion": "1.20.5" }, "version": 0.4 } \ No newline at end of file diff --git a/Sample_Workflows/Warped Hunyuan T2V Workflow.json b/Sample_Workflows/Warped Hunyuan T2V Workflow.json index cbee8e6..f3b0d81 100644 --- a/Sample_Workflows/Warped Hunyuan T2V Workflow.json +++ b/Sample_Workflows/Warped Hunyuan T2V Workflow.json @@ -1,56 +1,9 @@ { "id": "facb86ea-4076-46ff-921d-e6dcadfb1754", "revision": 0, - "last_node_id": 286, - "last_link_id": 904, + "last_node_id": 289, + "last_link_id": 908, "nodes": [ - { - "id": 152, - "type": "PreviewTextNode", - "pos": [ - 743.6790771484375, - -206.6033477783203 - ], - "size": [ - 342.3085632324219, - 114.48214721679688 - ], - "flags": {}, - "order": 18, - "mode": 0, - "inputs": [ - { - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 894 - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": null - } - ], - "properties": { - "cnr_id": "comfyui_custom_nodes_alekpet", - "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", - "Node name for S&R": "PreviewTextNode" - }, - "widgets_values": [ - "", - "****** WarpedSamplerCustomAdv: Total Images Generated 1 ******", - [ - false, - true - ] - ], - "color": "#322", - "bgcolor": "#533" - }, { "id": 99, "type": "Fast Groups Muter (rgthree)", @@ -143,7 +96,6 @@ "outputs": [ { "name": "CONDITIONING", - "shape": 3, "type": "CONDITIONING", "slot_index": 0, "links": [ @@ -193,7 +145,6 @@ "outputs": [ { "name": "GUIDER", - "shape": 3, "type": "GUIDER", "slot_index": 0, "links": [ @@ -330,7 +281,7 @@ "links": [ 526, 883, - 902 + 906 ] } ], @@ -442,7 +393,6 @@ "outputs": [ { "name": "SAMPLER", - "shape": 3, "type": "SAMPLER", "slot_index": 0, "links": [ @@ -540,6 +490,175 @@ } } }, + { + "id": 170, + "type": "StringConstantMultiline", + "pos": [ + -128.29852294921875, + 280.7070617675781 + ], + "size": [ + 423.325927734375, + 409.93310546875 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "slot_index": 0, + "links": [ + 448 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "e3b4bcf21e03402a2b11927eb5a4e3df500795b7", + "Node name for S&R": "StringConstantMultiline" + }, + "widgets_values": [ + "8itchW4lk, dynamic (real life:1.15) video showing front view of a woman with long flowing blonde_hair and bright green_eyes, she is wearing (beige thigh-high stockings and a maroon garter_belt with no panties and she is topless and she is braless:1.2). (she is showing her (large:1.3) ((natural heavy:1.3)) naked breasts:1.15) pointy, large aereolas, (her nipples are erect:1.25), She has small hips and a (small waist:1.4), (she is also wearing a thin diamond choker and navel_piercing, She has a slender fit body. she is in an abandonned nuclear facility, she was surprised by the viewer, so now she is walking toward the viewer to seduce him:1.25), (she is walking toward the viewer:1.2), (she is walking in a very sexy manner, looking at the viewer. Her facial expression goes from puzzled to flirting with the viewer. sexy but dangerous attitude and seductive lips, normal speed, her heavy breasts (((are (bouncing:1.5) and (swaying:1.3) and (jiggling:1.7):1.7)) unrestrained with every step she takes and with ever move she makes:1.15), (((her breasts bounce into each other and recoil away from each other as she walks:1.8))), the viewer keeps her in view all the times, (extremely soft breasts:1.3), (extremely bouncing breasts:1.3), (breasts bouncing up and down side to side:1.5), tanned skin, tanlines, detailed face, detailed hands, detailed fingers, nail polish, glamour eye makeup, eyeliner, lipstick, mascara, very petite body, 4k ultra high quality video footage, (realisic jiggle, bouncing, wobble, swaying physics, her breasts jiggle and bounce with each stap she takes:1.2), dark atmosphere, flickering lights shine down from the ceiling, well lit post apocalyptic nuclear building interior with god rays illuminating the room through holes in the ceiling and walls, retro cyberpunk vibe", + true, + true + ] + }, + { + "id": 286, + "type": "WarpedVAELoader", + "pos": [ + -439.18890380859375, + 438.5068359375 + ], + "size": [ + 315, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "vae", + "type": "VAE", + "links": [ + 904 + ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null + } + ], + "properties": { + "cnr_id": "ComfyUI-WarpedToolset", + "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "Node name for S&R": "WarpedVAELoader", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "hunyuan_video_vae_bf16.safetensors" + ], + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 67, + "type": "ModelSamplingSD3", + "pos": [ + -16.971450805664062, + 63.993743896484375 + ], + "size": [ + 210, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 905 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 195 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.27", + "Node name for S&R": "ModelSamplingSD3" + }, + "widgets_values": [ + 12 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 287, + "type": "MagCache", + "pos": [ + -19.923995971679688, + 4.6010003089904785 + ], + "size": [ + 270, + 178 + ], + "flags": { + "collapsed": true + }, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 906 + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "links": [ + 905 + ] + } + ], + "properties": { + "aux_id": "Zehong-Ma/ComfyUI-MagCache", + "ver": "7d4e982bf7955498afca891c7094c48a70985537", + "Node name for S&R": "MagCache" + }, + "widgets_values": [ + "hunyuan_video", + 0.12000000000000002, + 0.2, + 3, + 0, + -1 + ] + }, { "id": 284, "type": "WarpedCreateEmptyLatentBatch", @@ -552,7 +671,7 @@ 126 ], "flags": {}, - "order": 4, + "order": 6, "mode": 0, "inputs": [], "outputs": [ @@ -575,13 +694,56 @@ "Node name for S&R": "WarpedCreateEmptyLatentBatch" }, "widgets_values": [ - 137, - 320, - 512 + 121, + 480, + 720 ], "color": "#223", "bgcolor": "#335" }, + { + "id": 152, + "type": "PreviewTextNode", + "pos": [ + 743.6790771484375, + -233.2232208251953 + ], + "size": [ + 342.3085632324219, + 114.48214721679688 + ], + "flags": {}, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 894 + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode" + }, + "widgets_values": [ + "", + [ + false, + true + ] + ], + "color": "#322", + "bgcolor": "#533" + }, { "id": 280, "type": "WarpedSamplerCustomAdvLatent", @@ -639,7 +801,9 @@ { "name": "seed", "type": "INT", - "links": null + "links": [ + 907 + ] }, { "name": "generation_status", @@ -670,165 +834,111 @@ "bgcolor": "#535" }, { - "id": 170, - "type": "StringConstantMultiline", + "id": 289, + "type": "PreviewTextNode", "pos": [ - -128.29852294921875, - 280.7070617675781 + 1383.5662841796875, + -242.86416625976562 ], "size": [ - 423.325927734375, - 409.93310546875 + 242.4835662841797, + 103.83414459228516 ], "flags": {}, - "order": 5, + "order": 20, "mode": 0, - "inputs": [], + "inputs": [ + { + "name": "text", + "type": "STRING", + "link": 908 + } + ], "outputs": [ { "name": "STRING", "type": "STRING", - "slot_index": 0, - "links": [ - 448 - ] + "links": null } ], "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "e3b4bcf21e03402a2b11927eb5a4e3df500795b7", - "Node name for S&R": "StringConstantMultiline" + "cnr_id": "comfyui_custom_nodes_alekpet", + "ver": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", + "Node name for S&R": "PreviewTextNode" }, "widgets_values": [ - "8itchW4lk, dynamic (real life:1.15) video showing front view of a woman with long flowing blonde_hair and bright green_eyes, she is wearing (beige thigh-high stockings and a maroon garter_belt with no panties and she is topless and she is braless:1.2). (she is showing her (large:1.3) ((natural heavy:1.3)) naked breasts:1.15) pointy, large aereolas, (her nipples are erect:1.25), She has small hips and a (small waist:1.4), (she is also wearing a thin diamond choker and navel_piercing, She has a slender fit body. she is in an abandonned nuclear facility, she was surprised by the viewer, so now she is walking toward the viewer to seduce him:1.25), (she is walking toward the viewer:1.2), (she is walking in a very sexy manner, looking at the viewer. Her facial expression goes from puzzled to flirting with the viewer. sexy but dangerous attitude and seductive lips, normal speed, her heavy breasts (((are (bouncing:1.5) and (swaying:1.3) and (jiggling:1.7):1.7)) unrestrained with every step she takes and with ever move she makes:1.15), (((her breasts bounce into each other and recoil away from each other as she walks:1.8))), the viewer keeps her in view all the times, (extremely soft breasts:1.3), (extremely bouncing breasts:1.3), (breasts bouncing up and down side to side:1.5), tanned skin, tanlines, detailed face, detailed hands, detailed fingers, nail polish, glamour eye makeup, eyeliner, lipstick, mascara, very petite body, 4k ultra high quality video footage, (realisic jiggle, bouncing, wobble, swaying physics, her breasts jiggle and bounce with each stap she takes:1.2), dark atmosphere, flickering lights shine down from the ceiling, well lit post apocalyptic nuclear building interior with god rays illuminating the room through holes in the ceiling and walls, retro cyberpunk vibe", - true, - true - ] + "", + [ + false, + true + ] + ], + "color": "#322", + "bgcolor": "#533" }, { - "id": 285, - "type": "WarpedTeaCache", + "id": 288, + "type": "WarpedNumericalConversion", "pos": [ - -114.02001953125, - 13.55999755859375 + 1118.0810546875, + -229.65499877929688 ], "size": [ - 315, - 106 + 230.00741577148438, + 86 ], - "flags": { - "collapsed": true - }, - "order": 13, + "flags": {}, + "order": 18, "mode": 0, "inputs": [ { - "name": "model", - "type": "MODEL", - "link": 902 + "name": "int_value", + "shape": 7, + "type": "INT", + "link": 907 + }, + { + "name": "float_value", + "shape": 7, + "type": "FLOAT", + "link": null + }, + { + "name": "number_value", + "shape": 7, + "type": "NUMBER", + "link": null } ], "outputs": [ { - "name": "model", - "type": "MODEL", + "name": "int", + "type": "INT", + "links": null + }, + { + "name": "float", + "type": "FLOAT", + "links": null + }, + { + "name": "number", + "type": "NUMBER", + "links": null + }, + { + "name": "string", + "type": "STRING", "links": [ - 903 + 908 ] } ], "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedTeaCache" + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", + "Node name for S&R": "WarpedNumericalConversion" }, - "widgets_values": [ - "hunyuan_video", - 0.4, - 1 - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 67, - "type": "ModelSamplingSD3", - "pos": [ - 82.85357666015625, - 14.746726036071777 - ], - "size": [ - 210, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 14, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 903 - } - ], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "slot_index": 0, - "links": [ - 195 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.27", - "Node name for S&R": "ModelSamplingSD3" - }, - "widgets_values": [ - 12 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 286, - "type": "WarpedVAELoader", - "pos": [ - -439.18890380859375, - 438.5068359375 - ], - "size": [ - 315, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "VAE", - "type": "VAE", - "links": [ - 904 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedVAELoader" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors" - ], "color": "#323", "bgcolor": "#535" }, @@ -870,7 +980,7 @@ "rejects_ue_links": true }, "widgets_values": [ - "Hunyuan\\RsmFunbagsHy.safetensors", + "RsmHy\\RsmFunbagsHy.safetensors", 1.0000000000000002, "all", "None", @@ -1006,22 +1116,6 @@ 0, "LATENT" ], - [ - 902, - 81, - 0, - 285, - 0, - "MODEL" - ], - [ - 903, - 285, - 0, - 67, - 0, - "MODEL" - ], [ 904, 286, @@ -1029,6 +1123,38 @@ 195, 0, "*" + ], + [ + 905, + 287, + 0, + 67, + 0, + "MODEL" + ], + [ + 906, + 81, + 0, + 287, + 0, + "MODEL" + ], + [ + 907, + 280, + 2, + 288, + 0, + "INT" + ], + [ + 908, + 288, + 3, + 289, + 0, + "STRING" ] ], "groups": [ @@ -1051,14 +1177,14 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - 536.352, - 72.5969999999924 + 541.6760000000013, + 149.79499999999234 ] }, "groupNodes": {}, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "node_versions": { "comfy-core": "0.3.13", "comfyui-frame-interpolation": "1.0.6", @@ -1073,7 +1199,8 @@ "Hunyuan-Multi-Lora-Loader": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce" }, "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true + "VHS_KeepIntermediate": true, + "frontendVersion": "1.20.5" }, "version": 0.4 } \ No newline at end of file diff --git a/Sample_Workflows/Warped Hunyuan v2v Workflow.json b/Sample_Workflows/Warped Hunyuan v2v Workflow.json index 6cea76d..d62fde8 100644 --- a/Sample_Workflows/Warped Hunyuan v2v Workflow.json +++ b/Sample_Workflows/Warped Hunyuan v2v Workflow.json @@ -1,8 +1,8 @@ { "id": "cd4d8b23-c2f1-4f41-b28f-b13b2b63a361", "revision": 0, - "last_node_id": 302, - "last_link_id": 955, + "last_node_id": 303, + "last_link_id": 957, "nodes": [ { "id": 152, @@ -22,9 +22,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 920 } ], @@ -42,7 +39,6 @@ }, "widgets_values": [ "", - "****** WarpedSamplerCustomAdv: Total Images Generated 129 ******", [ false, true @@ -108,7 +104,6 @@ "outputs": [ { "name": "CONDITIONING", - "shape": 3, "type": "CONDITIONING", "slot_index": 0, "links": [ @@ -147,9 +142,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 939 } ], @@ -184,7 +176,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", dark skin,", ", tanned skin," ] @@ -220,7 +211,6 @@ "outputs": [ { "name": "GUIDER", - "shape": 3, "type": "GUIDER", "slot_index": 0, "links": [ @@ -339,7 +329,7 @@ "links": [ 526, 942, - 948 + 956 ] } ], @@ -558,18 +548,12 @@ "name": "prompt1", "shape": 7, "type": "STRING", - "widget": { - "name": "prompt1" - }, "link": 933 }, { "name": "prompt2", "shape": 7, "type": "STRING", - "widget": { - "name": "prompt2" - }, "link": 934 } ], @@ -589,8 +573,6 @@ "Node name for S&R": "easy promptConcat" }, "widgets_values": [ - "", - "", "" ], "color": "#233", @@ -834,9 +816,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 936 } ], @@ -871,7 +850,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", censored,", "," ] @@ -896,9 +874,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 946 } ], @@ -933,7 +908,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", dark-skinned female,", "," ] @@ -958,9 +932,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 938 } ], @@ -995,7 +966,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", "1girl,", "A video of a beautiful gorgeous stunning young woman who is straddling a man's penis and fucking him cowgirl style," ] @@ -1020,7 +990,6 @@ "outputs": [ { "name": "SAMPLER", - "shape": 3, "type": "SAMPLER", "slot_index": 0, "links": [ @@ -1101,9 +1070,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 945 } ], @@ -1138,7 +1104,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", small_breasts,", ", (((large_breasts:1.3)))," ] @@ -1215,9 +1180,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 944 } ], @@ -1252,63 +1214,10 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", medium_breasts,", ", (((large_breasts:1.3)))," ] }, - { - "id": 258, - "type": "HunyuanMultiLoraLoader", - "pos": [ - -482.0614929199219, - -28.159400939941406 - ], - "size": [ - 315, - 322 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 872 - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "slot_index": 0, - "links": [ - 921 - ] - } - ], - "properties": { - "cnr_id": "Hunyuan-Multi-Lora-Loader", - "ver": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce", - "Node name for S&R": "HunyuanMultiLoraLoader", - "rejects_ue_links": true - }, - "widgets_values": [ - "Hunyuan\\RsmFunbags2Hy.safetensors", - 0.75, - "all", - "Hunyuan\\HYVrewardMPS_epoch40.safetensors", - 1, - "double_blocks", - "None", - 1, - "all", - "None", - 1, - "all" - ] - }, { "id": 296, "type": "WarpedBundleVideoImages", @@ -1403,9 +1312,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 935 } ], @@ -1440,7 +1346,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", black_hair,", ", brown_hair," ] @@ -1465,9 +1370,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 903 } ], @@ -1502,7 +1404,6 @@ "Node name for S&R": "Text Find and Replace" }, "widgets_values": [ - "", ", blonde_hair,", ", brown_hair," ] @@ -1565,9 +1466,6 @@ { "name": "text", "type": "STRING", - "widget": { - "name": "text" - }, "link": 890 } ], @@ -1588,7 +1486,6 @@ }, "widgets_values": [ "", - "A video of a beautiful gorgeous stunning young woman who is straddling a man's penis and fucking him cowgirl style, long_hair, breasts, looking_at_viewer, brown_hair, hair_ornament, 1boy, navel, brown_eyes, nipples, hetero, nude, (((large_breasts:1.3))), penis, pussy, solo_focus, socks, spread_legs, sex, vaginal, lips, kneehighs, pubic_hair, uncensored, pov, female_pubic_hair, straddling, girl_on_top, realistic, cowgirl sex, sex, vaginal penetration, penis, uncensored, makeup, eyelioner, mascara, lipstick, thin silver pendant necklace, realistic, red nail_polish.", [ false, true @@ -1642,95 +1539,6 @@ true ] }, - { - "id": 300, - "type": "WarpedTeaCache", - "pos": [ - -143.18751525878906, - 31.289152145385742 - ], - "size": [ - 315, - 106 - ], - "flags": { - "collapsed": true - }, - "order": 21, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 948 - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "links": [ - 949 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedTeaCache" - }, - "widgets_values": [ - "hunyuan_video", - 0.4, - 1 - ], - "color": "#323", - "bgcolor": "#535" - }, - { - "id": 67, - "type": "ModelSamplingSD3", - "pos": [ - 48.302276611328125, - 31.061674118041992 - ], - "size": [ - 210, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 23, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 949 - } - ], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "slot_index": 0, - "links": [ - 195 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.27", - "Node name for S&R": "ModelSamplingSD3" - }, - "widgets_values": [ - 12 - ], - "color": "#222", - "bgcolor": "#000" - }, { "id": 280, "type": "VAEEncodeTiled", @@ -1800,19 +1608,24 @@ "inputs": [], "outputs": [ { - "name": "VAE", + "name": "vae", "type": "VAE", "links": [ 954, 955 ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null } ], "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedVAELoader" + "Node name for S&R": "WarpedVAELoader", + "widget_ue_connectable": {} }, "widgets_values": [ "hunyuan_video_vae_bf16.safetensors" @@ -1829,7 +1642,7 @@ ], "size": [ 315, - 190 + 214 ], "flags": {}, "order": 8, @@ -1876,10 +1689,153 @@ 512, 288, "lanczos", - "center" + "center", + false ], "color": "#323", "bgcolor": "#535" + }, + { + "id": 67, + "type": "ModelSamplingSD3", + "pos": [ + 7.041263103485107, + 41.70968246459961 + ], + "size": [ + 210, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 957 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 195 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.27", + "Node name for S&R": "ModelSamplingSD3" + }, + "widgets_values": [ + 12 + ], + "color": "#222", + "bgcolor": "#000" + }, + { + "id": 303, + "type": "MagCache", + "pos": [ + 10.688987731933594, + -4.716006278991699 + ], + "size": [ + 270, + 178 + ], + "flags": { + "collapsed": true + }, + "order": 21, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 956 + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "links": [ + 957 + ] + } + ], + "properties": { + "aux_id": "Zehong-Ma/ComfyUI-MagCache", + "ver": "7d4e982bf7955498afca891c7094c48a70985537", + "Node name for S&R": "MagCache" + }, + "widgets_values": [ + "hunyuan_video", + 0.12000000000000002, + 0.2, + 3, + 0, + -1 + ] + }, + { + "id": 258, + "type": "HunyuanMultiLoraLoader", + "pos": [ + -482.0614929199219, + -28.159400939941406 + ], + "size": [ + 315, + 322 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 872 + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "slot_index": 0, + "links": [ + 921 + ] + } + ], + "properties": { + "cnr_id": "Hunyuan-Multi-Lora-Loader", + "ver": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce", + "Node name for S&R": "HunyuanMultiLoraLoader", + "rejects_ue_links": true + }, + "widgets_values": [ + "RsmHy\\RsmFunbagsHy.safetensors", + 0.75, + "all", + "Hunyuan\\HYVrewardMPS_epoch40.safetensors", + 1, + "double_blocks", + "None", + 1, + "all", + "None", + 1, + "all" + ] } ], "links": [ @@ -2139,22 +2095,6 @@ 0, "IMAGE" ], - [ - 948, - 81, - 0, - 300, - 0, - "MODEL" - ], - [ - 949, - 300, - 0, - 67, - 0, - "MODEL" - ], [ 950, 296, @@ -2202,6 +2142,22 @@ 195, 0, "*" + ], + [ + 956, + 81, + 0, + 303, + 0, + "MODEL" + ], + [ + 957, + 303, + 0, + 67, + 0, + "MODEL" ] ], "groups": [ @@ -2224,14 +2180,14 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - 634.8460000000013, - -278.78700000000777 + 540.3449999999999, + 148.46399999999193 ] }, "groupNodes": {}, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "node_versions": { "ComfyUI_Custom_Nodes_AlekPet": "7b3d6f190aeca261422bdfb74b5af37937e5bf68", "comfy-core": "0.3.26", @@ -2245,7 +2201,8 @@ "Hunyuan-Multi-Lora-Loader": "dd009a9d44b47bfaf01a4ffa38d7098f523e2cce" }, "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true + "VHS_KeepIntermediate": true, + "frontendVersion": "1.20.5" }, "version": 0.4 } \ No newline at end of file diff --git a/Sample_Workflows/Warped Multi LORA Mixer Extended Workflow.json b/Sample_Workflows/Warped Multi LORA Mixer Extended Workflow.json index 7105f61..4cbeaf2 100644 --- a/Sample_Workflows/Warped Multi LORA Mixer Extended Workflow.json +++ b/Sample_Workflows/Warped Multi LORA Mixer Extended Workflow.json @@ -1,19 +1,19 @@ { "id": "00000000-0000-0000-0000-000000000000", "revision": 0, - "last_node_id": 1, + "last_node_id": 2, "last_link_id": 0, "nodes": [ { - "id": 1, + "id": 2, "type": "WarpedHunyuanMultiLoraMixerExt", "pos": [ - 708, - 225 + 496.5042419433594, + 196.2649383544922 ], "size": [ - 441, - 562 + 515.6026000976562, + 639.281982421875 ], "flags": {}, "order": 0, @@ -21,12 +21,12 @@ "inputs": [], "outputs": [], "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", + "cnr_id": "comfyui-warpedtoolset", + "ver": "1.7.0", "Node name for S&R": "WarpedHunyuanMultiLoraMixerExt" }, "widgets_values": [ - "M:\\Projects\\ComfyUI_windows_portable\\ComfyUI\\output\\MergedHunyuanLoras", + "N:\\Projects\\ComfyUI_windows_portable\\ComfyUI\\output\\MergedHunyuanLoras", "new_model_hy", 0, "randomize", @@ -47,7 +47,9 @@ 1, "None", 1, - true + true, + "diffusion_model", + 32 ], "color": "#323", "bgcolor": "#535" @@ -66,9 +68,10 @@ }, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true + "VHS_KeepIntermediate": true, + "frontendVersion": "1.20.5" }, "version": 0.4 } \ No newline at end of file diff --git a/Sample_Workflows/Warped Reverse Video Workflow.json b/Sample_Workflows/Warped Reverse Video Workflow.json new file mode 100644 index 0000000..32f919a --- /dev/null +++ b/Sample_Workflows/Warped Reverse Video Workflow.json @@ -0,0 +1,231 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "revision": 0, + "last_node_id": 11, + "last_link_id": 5, + "nodes": [ + { + "id": 9, + "type": "WarpedReverseImageBatch", + "pos": [ + 847, + 329 + ], + "size": [ + 226.91073608398438, + 26 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 3 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 4 + ] + } + ], + "properties": { + "Node name for S&R": "WarpedReverseImageBatch" + }, + "color": "#323", + "bgcolor": "#535" + }, + { + "id": 11, + "type": "VHS_VideoCombine", + "pos": [ + 1200, + 329 + ], + "size": [ + 504.7587890625, + 736 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 4 + }, + { + "name": "audio", + "shape": 7, + "type": "AUDIO", + "link": null + }, + { + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + }, + { + "name": "frame_rate", + "type": "FLOAT", + "widget": { + "name": "frame_rate" + }, + "link": 5 + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "1.6.1", + "Node name for S&R": "VHS_VideoCombine" + }, + "widgets_values": { + "frame_rate": 8, + "loop_count": 0, + "filename_prefix": "Hunyuan/%date:yyyy_MM_dd%/lowres/vid", + "format": "video/nvenc_h264-mp4", + "pix_fmt": "yuv420p", + "bitrate": 10, + "megabit": true, + "save_metadata": true, + "pingpong": false, + "save_output": true, + "save_images": false, + "videopreview": { + "hidden": false, + "paused": false, + "params": {} + } + } + }, + { + "id": 10, + "type": "WarpedBundleAllVideoImages", + "pos": [ + 418, + 331 + ], + "size": [ + 304.5747985839844, + 202 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "image_batch", + "type": "IMAGE", + "links": [ + 3 + ] + }, + { + "name": "first_image", + "type": "IMAGE", + "links": null + }, + { + "name": "last_image", + "type": "IMAGE", + "links": null + }, + { + "name": "num_frames", + "type": "INT", + "links": null + }, + { + "name": "width", + "type": "INT", + "links": null + }, + { + "name": "height", + "type": "INT", + "links": null + }, + { + "name": "fps", + "type": "FLOAT", + "links": [ + 5 + ] + } + ], + "properties": { + "Node name for S&R": "WarpedBundleAllVideoImages" + }, + "widgets_values": [ + "", + true + ], + "color": "#323", + "bgcolor": "#535" + } + ], + "links": [ + [ + 3, + 10, + 0, + 9, + 0, + "IMAGE" + ], + [ + 4, + 9, + 0, + 11, + 0, + "IMAGE" + ], + [ + 5, + 10, + 6, + 11, + 4, + "FLOAT" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 1, + "offset": [ + -126, + -214 + ] + }, + "frontendVersion": "1.20.5", + "VHS_latentpreview": true, + "VHS_latentpreviewrate": 15, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/Sample_Workflows/Warped WAN I2V Workflow.json b/Sample_Workflows/Warped WAN I2V Workflow.json index 93b0cd8..f71bb68 100644 --- a/Sample_Workflows/Warped WAN I2V Workflow.json +++ b/Sample_Workflows/Warped WAN I2V Workflow.json @@ -1,8 +1,8 @@ { "id": "2fac44f1-8935-4d17-8233-fe2dcaee9ba8", "revision": 0, - "last_node_id": 139, - "last_link_id": 295, + "last_node_id": 140, + "last_link_id": 296, "nodes": [ { "id": 38, @@ -13,7 +13,7 @@ ], "size": [ 376.05950927734375, - 100.8628921508789 + 106 ], "flags": {}, "order": 0, @@ -86,7 +86,7 @@ 90.64099884033203 ], "flags": {}, - "order": 10, + "order": 12, "mode": 0, "inputs": [ { @@ -209,7 +209,7 @@ 134.78460693359375 ], "flags": {}, - "order": 6, + "order": 7, "mode": 0, "inputs": [ { @@ -304,7 +304,7 @@ "flags": { "collapsed": true }, - "order": 8, + "order": 10, "mode": 0, "inputs": [ { @@ -346,7 +346,7 @@ 284.9806823730469 ], "flags": {}, - "order": 9, + "order": 11, "mode": 0, "inputs": [ { @@ -378,48 +378,6 @@ "color": "#232", "bgcolor": "#353" }, - { - "id": 110, - "type": "ModelSamplingSD3", - "pos": [ - 195.28842163085938, - -359.64794921875 - ], - "size": [ - 315, - 58 - ], - "flags": { - "collapsed": true - }, - "order": 18, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 262 - } - ], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "slot_index": 0, - "links": [ - 259 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.27", - "Node name for S&R": "ModelSamplingSD3" - }, - "widgets_values": [ - 12 - ] - }, { "id": 109, "type": "BasicGuider", @@ -480,7 +438,7 @@ "flags": { "collapsed": true }, - "order": 15, + "order": 17, "mode": 0, "inputs": [ { @@ -604,52 +562,6 @@ "color": "#223", "bgcolor": "#335" }, - { - "id": 133, - "type": "WarpedTeaCache", - "pos": [ - 3.7849433422088623, - -360.75299072265625 - ], - "size": [ - 315, - 106 - ], - "flags": { - "collapsed": true - }, - "order": 17, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 274 - } - ], - "outputs": [ - { - "name": "model", - "type": "MODEL", - "slot_index": 0, - "links": [ - 262 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-WarpedToolset", - "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "Node name for S&R": "WarpedTeaCache" - }, - "widgets_values": [ - "wan2.1_i2v_720p_14B", - 0.4, - 1 - ], - "color": "#323", - "bgcolor": "#535" - }, { "id": 135, "type": "WanVideoEnhanceAVideoKJ", @@ -664,7 +576,7 @@ "flags": { "collapsed": true }, - "order": 16, + "order": 18, "mode": 0, "inputs": [ { @@ -683,9 +595,7 @@ "name": "model", "type": "MODEL", "slot_index": 0, - "links": [ - 274 - ] + "links": [] } ], "properties": { @@ -753,7 +663,7 @@ 517.2369384765625 ], "flags": {}, - "order": 12, + "order": 14, "mode": 0, "inputs": [ { @@ -782,7 +692,7 @@ 126 ], "flags": {}, - "order": 11, + "order": 13, "mode": 0, "inputs": [ { @@ -832,10 +742,10 @@ ], "size": [ 315, - 190 + 214 ], "flags": {}, - "order": 7, + "order": 8, "mode": 0, "inputs": [ { @@ -883,7 +793,8 @@ 400, 720, "lanczos", - "center" + "center", + false ], "color": "#323", "bgcolor": "#535" @@ -902,7 +813,7 @@ "flags": { "collapsed": true }, - "order": 13, + "order": 15, "mode": 0, "inputs": [ { @@ -947,7 +858,7 @@ 250 ], "flags": {}, - "order": 14, + "order": 16, "mode": 0, "inputs": [ { @@ -985,14 +896,6 @@ }, "link": 285 }, - { - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": null - }, { "name": "height", "type": "INT", @@ -1068,7 +971,7 @@ "inputs": [], "outputs": [ { - "name": "CLIP_VISION", + "name": "clip_vision", "type": "CLIP_VISION", "links": [ 289 @@ -1078,8 +981,8 @@ "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedCLIPVisionLoader" + "Node name for S&R": "WarpedCLIPVisionLoader", + "widget_ue_connectable": {} }, "widgets_values": [ "clip_vision_h_fp8_e4m3fn.safetensors" @@ -1106,25 +1009,118 @@ "inputs": [], "outputs": [ { - "name": "VAE", + "name": "vae", "type": "VAE", "links": [ 290, 291 ] + }, + { + "name": "vae_state_dict", + "type": "DICT", + "links": null } ], "properties": { "cnr_id": "ComfyUI-WarpedToolset", "ver": "4e08ee52f8f8f1b9b672937866812d63bbb520eb", - "widget_ue_connectable": {}, - "Node name for S&R": "WarpedVAELoader" + "Node name for S&R": "WarpedVAELoader", + "widget_ue_connectable": {} }, "widgets_values": [ "wan_2.1_vae_fp8_e4m3fn.safetensors" ], "color": "#323", "bgcolor": "#535" + }, + { + "id": 140, + "type": "MagCache", + "pos": [ + -13.26900863647461, + -361.42431640625 + ], + "size": [ + 270, + 178 + ], + "flags": { + "collapsed": true + }, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": null + } + ], + "outputs": [ + { + "name": "model", + "type": "MODEL", + "links": [ + 296 + ] + } + ], + "properties": { + "aux_id": "Zehong-Ma/ComfyUI-MagCache", + "ver": "7d4e982bf7955498afca891c7094c48a70985537", + "Node name for S&R": "MagCache" + }, + "widgets_values": [ + "wan2.1_i2v_720p_14B", + 0.12000000000000002, + 0.2, + 3, + 0, + -1 + ] + }, + { + "id": 110, + "type": "ModelSamplingSD3", + "pos": [ + 189.9645538330078, + -359.64776611328125 + ], + "size": [ + 315, + 58 + ], + "flags": { + "collapsed": true + }, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 296 + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [ + 259 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.27", + "Node name for S&R": "ModelSamplingSD3" + }, + "widgets_values": [ + 12 + ] } ], "links": [ @@ -1184,14 +1180,6 @@ 0, "MODEL" ], - [ - 262, - 133, - 0, - 110, - 0, - "MODEL" - ], [ 263, 6, @@ -1248,14 +1236,6 @@ 0, "MODEL" ], - [ - 274, - 135, - 0, - 133, - 0, - "MODEL" - ], [ 275, 134, @@ -1341,7 +1321,7 @@ 137, 3, 134, - 7, + 6, "INT" ], [ @@ -1383,6 +1363,14 @@ 134, 2, "VAE" + ], + [ + 296, + 140, + 0, + 110, + 0, + "MODEL" ] ], "groups": [], @@ -1391,10 +1379,11 @@ "ds": { "scale": 0.7513148009015777, "offset": [ - 158.34800000000385, - 168.428999999993 + 159.67900000000375, + 451.9319999999936 ] }, + "frontendVersion": "1.20.5", "node_versions": { "comfyui-easy-use": "1.2.7", "comfy-core": "0.3.26", @@ -1405,7 +1394,7 @@ }, "ue_links": [], "VHS_latentpreview": true, - "VHS_latentpreviewrate": 10, + "VHS_latentpreviewrate": 15, "VHS_MetadataImage": true, "VHS_KeepIntermediate": true }, diff --git a/__init__.py b/__init__.py index 761bd68..8a34381 100644 --- a/__init__.py +++ b/__init__.py @@ -6,27 +6,29 @@ NODE_CLASS_MAPPINGS = { "WarpedHunyuanLoraMerge": WarpedHunyuanLoraMerge, "WarpedHunyuanMultiLoraMixer": WarpedHunyuanMultiLoraMixer, "WarpedHunyuanMultiLoraMixerExt": WarpedHunyuanMultiLoraMixerExt, + # "WarpedHunyuanMultiLoraMixerExt2": WarpedHunyuanMultiLoraMixerExt2, "WarpedHunyuanLoraAvgMerge": WarpedHunyuanLoraAvgMerge, "WarpedHunyuanLoraBatchMerge": WarpedHunyuanLoraBatchMerge, "WarpedWanLoraMerge": WarpedWanLoraMerge, "WarpedSamplerCustomAdv": WarpedSamplerCustomAdv, "WarpedSamplerCustomAdvLatent": WarpedSamplerCustomAdvLatent, + # "WarpedAdvancedSamplerBatch": WarpedAdvancedSamplerBatch, "WarpedFramepackSampler": WarpedFramepackSampler, "WarpedCreateSpecialImageBatch": WarpedCreateSpecialImageBatch, "WarpedCreateEmptyImageBatch": WarpedCreateEmptyImageBatch, "WarpedCreateEmptyLatentBatch": WarpedCreateEmptyLatentBatch, "WarpedBundleVideoImages": WarpedBundleVideoImages, "WarpedBundleAllVideoImages": WarpedBundleAllVideoImages, + "WarpedGetImageFromVideo": WarpedGetImageFromVideo, + "WarpedGetTwoImagesFromVideo": WarpedGetTwoImagesFromVideo, "WarpedImageNoiseAugmentation": WarpedImageNoiseAugmentation, "WarpedLeapfusionHunyuanI2V": WarpedLeapfusionHunyuanI2V, "WarpedSaveAnimatedPng": WarpedSaveAnimatedPng, "WarpedUpscaleWithModel": WarpedUpscaleWithModel, "WarpedLoadVideosBatch": WarpedLoadVideosBatch, "WarpedWanImageToVideo": WarpedWanImageToVideo, - "WarpedTeaCache": WarpedTeaCache, "WarpedWanLoadAndEditLoraBlocks": WarpedWanLoadAndEditLoraBlocks, - "WarpedImageResize": WarpedImageResize, - "WarpedImageResizeKJStyle": WarpedImageResizeKJStyle, + # "WarpedImageResize": WarpedImageResize, "WarpedImageScaleToSide": WarpedImageScaleToSide, "WarpedLoadLorasBatchByPrefix": WarpedLoadLorasBatchByPrefix, "WarpedHunyuanVideoLoraLoader": WarpedHunyuanVideoLoraLoader, @@ -36,11 +38,18 @@ NODE_CLASS_MAPPINGS = { "WarpedHunyuanLoraConvert": WarpedHunyuanLoraConvert, "WarpedFramepackLoraSelectBatch": WarpedFramepackLoraSelectBatch, "WarpedFramepackMultiLoraSelect": WarpedFramepackMultiLoraSelect, + "WarpedFramepackMultiLoraSelectExt": WarpedFramepackMultiLoraSelectExt, "WarpedDualCLIPLoader": WarpedDualCLIPLoader, "WarpedDualEncoder": WarpedDualEncoder, "WarpedCLIPVisionLoader": WarpedCLIPVisionLoader, "WarpedVAELoader": WarpedVAELoader, "WarpedLoadFramePackModel": WarpedLoadFramePackModel, + "WarpedNumericalConversion": WarpedNumericalConversion, + "WarpedLoraReSave": WarpedLoraReSave, + "WarpedCheckpointLoader": WarpedCheckpointLoader, + "WarpedReverseImageBatch": WarpedReverseImageBatch, + "WarpedMultiLoraLoader": WarpedMultiLoraLoader, + "WarpedHunyuanImageToVideo": WarpedHunyuanImageToVideo, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -49,27 +58,29 @@ NODE_DISPLAY_NAME_MAPPINGS = { "WarpedHunyuanLoraMerge": "Warped Hunyuan Lora Merge", "WarpedHunyuanMultiLoraMixer": "Warped Hunyuan Multi Lora Mixer", "WarpedHunyuanMultiLoraMixerExt": "Warped Hunyuan Multi Lora Mixer Ext", + # "WarpedHunyuanMultiLoraMixerExt2": "Warped Hunyuan Multi Lora Mixer Ext 2", "WarpedHunyuanLoraAvgMerge": "Warped Hunyuan Lora Avg Merge", "WarpedHunyuanLoraBatchMerge": "Warped Hunyuan Lora Batch Merge", "WarpedWanLoraMerge": "Warped Wan Lora Merge", "WarpedSamplerCustomAdv": "Warped Sampler Custom Advanced (Image)", "WarpedSamplerCustomAdvLatent": "Warped Sampler Custom Advanced (Latent)", + # "WarpedAdvancedSamplerBatch": "Warped Advanced Sampler Batch", "WarpedFramepackSampler": "Warped Framepack Sampler", "WarpedCreateSpecialImageBatch": "Warped Create Special Image Batch", "WarpedCreateEmptyImageBatch": "Warped Create Empty Image Batch", "WarpedCreateEmptyLatentBatch": "Warped Create Empty Latent Batch", "WarpedBundleVideoImages": "Warped Bundle Video Images", "WarpedBundleAllVideoImages": "Warped Bundle All Video Images", + "WarpedGetImageFromVideo": "Warped Get Image From Video", + "WarpedGetTwoImagesFromVideo": "Warped Get Two Images From Video", "WarpedImageNoiseAugmentation": "Warped Image Noise Augmentation", "WarpedLeapfusionHunyuanI2V": "Warped Leapfusion Hunyuan I2V", "WarpedSaveAnimatedPng": "Warped Save Animated Png", "WarpedUpscaleWithModel": "Warped Upscale With Model", "WarpedLoadVideosBatch": "Warped Load Videos Batch", "WarpedWanImageToVideo": "Warped Wan Image To Video", - "WarpedTeaCache": "Warped TeaCache", "WarpedWanLoadAndEditLoraBlocks": "Warped Wan Load And Edit Lora Blocks", - "WarpedImageResize": "Warped Image Resize", - "WarpedImageResizeKJStyle": "Warped Image Resize KJ Style", + # "WarpedImageResize": "Warped Image Resize", "WarpedImageScaleToSide": "Warped Image Scale To Side", "WarpedLoadLorasBatchByPrefix": "Warped Load Loras Batch By Prefix", "WarpedHunyuanVideoLoraLoader": "Warped Hunyuan Video Lora Loader", @@ -79,11 +90,18 @@ NODE_DISPLAY_NAME_MAPPINGS = { "WarpedHunyuanLoraConvert": "Warped Hunyuan Lora Convert", "WarpedFramepackLoraSelectBatch": "Warped Framepack Lora Select Batch", "WarpedFramepackMultiLoraSelect": "Warped Framepack Multi Lora Select", + "WarpedFramepackMultiLoraSelectExt": "Warped Framepack Multi Lora Select Ext", "WarpedDualCLIPLoader": "Warped Dual CLIP Loader", "WarpedDualEncoder": "Warped Dual Encoder", "WarpedCLIPVisionLoader": "Warped Load CLIP Vision", "WarpedVAELoader": "Warped Load VAE", "WarpedLoadFramePackModel": "Warped Load FramePack Model", + "WarpedNumericalConversion": "Warped Numerical Conversion", + "WarpedLoraReSave": "Warped Lora Re-Save", + "WarpedCheckpointLoader": "Warped Checkpoint Loader", + "WarpedReverseImageBatch": "Warped Reverse Image Batch", + "WarpedMultiLoraLoader": "Warped Multi Lora Loader", + "WarpedHunyuanImageToVideo": "Warped Hunyuan Image To Video", } WEB_DIRECTORY = "./web" diff --git a/diffusers_helper/k_diffusion_hunyuan.py b/diffusers_helper/k_diffusion_hunyuan.py index bbb86c7..f081452 100644 --- a/diffusers_helper/k_diffusion_hunyuan.py +++ b/diffusers_helper/k_diffusion_hunyuan.py @@ -23,7 +23,6 @@ def get_flux_sigmas_from_mu(n, mu): sigmas = flux_time_shift(sigmas, mu=mu) return sigmas - @torch.inference_mode() def sample_hunyuan2( transformer, diff --git a/diffusers_helper/models/hunyuan_video_packed.py b/diffusers_helper/models/hunyuan_video_packed.py index 03424a1..16448e7 100644 --- a/diffusers_helper/models/hunyuan_video_packed.py +++ b/diffusers_helper/models/hunyuan_video_packed.py @@ -774,6 +774,9 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, self.inner_dim = inner_dim self.use_gradient_checkpointing = False self.enable_teacache = False + self.enable_magcache = False + self.verbose_messaging = False + self.orig_forward = self.forward if has_image_proj: self.install_image_projection(image_proj_dim) @@ -800,7 +803,7 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, self.use_gradient_checkpointing = False print('self.use_gradient_checkpointing = False') - def initialize_teacache(self, enable_teacache=True, num_steps=25, rel_l1_thresh=0.15): + def initialize_teacache(self, enable_teacache=True, num_steps=25, rel_l1_thresh=0.15, verbose_messaging=False): self.enable_teacache = enable_teacache self.cnt = 0 self.num_steps = num_steps @@ -809,6 +812,32 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, self.previous_modulated_input = None self.previous_residual = None self.teacache_rescale_func = np.poly1d([7.33226126e+02, -4.01131952e+02, 6.75869174e+01, -3.14987800e+00, 9.61237896e-02]) + self.verbose_messaging = verbose_messaging + + def nearest_interp(self, src_array, target_length): + src_length = len(src_array) + if target_length == 1: + return np.array([src_array[-1]]) + + scale = (src_length - 1) / (target_length - 1) + mapped_indices = np.round(np.arange(target_length) * scale).astype(int) + return src_array[mapped_indices] + + def initialize_magcache(self, enable_magcache=True, num_steps=25, magcache_thresh=0.1, K=2, retention_ratio=0.2, verbose_messaging=False): + self.enable_magcache = enable_magcache + self.cnt = 0 + self.num_steps = num_steps + self.magcache_thresh = magcache_thresh + self.K = K + self.retention_ratio = retention_ratio + self.mag_ratios = np.array([1.0]+[1.25781, 1.08594, 1.02344, 1.00781, 1.02344, 1.00781, 1.02344, 1.05469, 0.99609, 1.03906, 1.00781, 1.01562, 1.00781, 1.02344, 1.01562, 0.98047, 1.05469, 0.98047, 0.96875, 1.03125, 0.97266, 0.9375, 0.96484, 0.78516]) + + # Nearest interpolation when the num_steps is different from the length of mag_ratios + if len(self.mag_ratios) != num_steps: + interpolated_mag_ratios = self.nearest_interp(self.mag_ratios, num_steps) + self.mag_ratios = interpolated_mag_ratios + + self.verbose_messaging = verbose_messaging def gradient_checkpointing_method(self, block, *args): if self.use_gradient_checkpointing: @@ -876,6 +905,162 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, return hidden_states, rope_freqs + def magcache_framepack_forward( + self, + hidden_states, timestep, encoder_hidden_states, encoder_attention_mask, pooled_projections, guidance, + latent_indices=None, + clean_latents=None, clean_latent_indices=None, + clean_latents_2x=None, clean_latent_2x_indices=None, + clean_latents_4x=None, clean_latent_4x_indices=None, + image_embeddings=None, + attention_kwargs=None, return_dict=True + ): + + if self.verbose_messaging: + print("\nMagCache Forward Entry...") + print("self.enable_magcache: {}".format(self.enable_magcache)) + print("self.cnt: {}".format(self.cnt)) + print("self.accumulated_ratio: {}".format(self.accumulated_ratio)) + print("self.accumulated_steps: {}".format(self.accumulated_steps)) + print("self.accumulated_err: {}".format(self.accumulated_err)) + + if attention_kwargs is None: + attention_kwargs = {} + + batch_size, num_channels, num_frames, height, width = hidden_states.shape + p, p_t = self.config['patch_size'], self.config['patch_size_t'] + post_patch_num_frames = num_frames // p_t + post_patch_height = height // p + post_patch_width = width // p + original_context_length = post_patch_num_frames * post_patch_height * post_patch_width + + hidden_states, rope_freqs = self.process_input_hidden_states(hidden_states, latent_indices, clean_latents, clean_latent_indices, clean_latents_2x, clean_latent_2x_indices, clean_latents_4x, clean_latent_4x_indices) + + temb = self.gradient_checkpointing_method(self.time_text_embed, timestep, guidance, pooled_projections) + encoder_hidden_states = self.gradient_checkpointing_method(self.context_embedder, encoder_hidden_states, timestep, encoder_attention_mask) + + if self.image_projection is not None: + assert image_embeddings is not None, 'You must use image embeddings!' + extra_encoder_hidden_states = self.gradient_checkpointing_method(self.image_projection, image_embeddings) + extra_attention_mask = torch.ones((batch_size, extra_encoder_hidden_states.shape[1]), dtype=encoder_attention_mask.dtype, device=encoder_attention_mask.device) + + # must cat before (not after) encoder_hidden_states, due to attn masking + encoder_hidden_states = torch.cat([extra_encoder_hidden_states, encoder_hidden_states], dim=1) + encoder_attention_mask = torch.cat([extra_attention_mask, encoder_attention_mask], dim=1) + + if batch_size == 1: + # When batch size is 1, we do not need any masks or var-len funcs since cropping is mathematically same to what we want + # If they are not same, then their impls are wrong. Ours are always the correct one. + text_len = encoder_attention_mask.sum().item() + encoder_hidden_states = encoder_hidden_states[:, :text_len] + attention_mask = None, None, None, None + else: + img_seq_len = hidden_states.shape[1] + txt_seq_len = encoder_hidden_states.shape[1] + + cu_seqlens_q = get_cu_seqlens(encoder_attention_mask, img_seq_len) + cu_seqlens_kv = cu_seqlens_q + max_seqlen_q = img_seq_len + txt_seq_len + max_seqlen_kv = max_seqlen_q + + attention_mask = cu_seqlens_q, cu_seqlens_kv, max_seqlen_q, max_seqlen_kv + + if self.enable_magcache: + if self.cnt == 0: # initialize MagCache + self.accumulated_ratio = 1.0 + self.accumulated_steps = 0 + self.accumulated_err = 0 + + skip_forward = False + if self.cnt>=int(self.retention_ratio*self.num_steps) and self.cnt>=1: # keep first retention_ratio steps + cur_mag_ratio = self.mag_ratios[self.cnt] + self.accumulated_ratio = self.accumulated_ratio*cur_mag_ratio + cur_skip_err = np.abs(1-self.accumulated_ratio) + self.accumulated_err += cur_skip_err + self.accumulated_steps += 1 + if self.accumulated_err<=self.magcache_thresh and self.accumulated_steps<=self.K and np.abs(1-cur_mag_ratio)<=0.06: + skip_forward = True + else: + self.accumulated_ratio = 1.0 + self.accumulated_steps = 0 + self.accumulated_err = 0 + + if skip_forward: + hidden_states = hidden_states + self.previous_residual + else: + ori_hidden_states = hidden_states.clone() + + for block_id, block in enumerate(self.transformer_blocks): + hidden_states, encoder_hidden_states = self.gradient_checkpointing_method( + block, + hidden_states, + encoder_hidden_states, + temb, + attention_mask, + rope_freqs + ) + + for block_id, block in enumerate(self.single_transformer_blocks): + hidden_states, encoder_hidden_states = self.gradient_checkpointing_method( + block, + hidden_states, + encoder_hidden_states, + temb, + attention_mask, + rope_freqs + ) + + self.previous_residual = hidden_states - ori_hidden_states + self.cnt += 1 + if self.cnt == self.num_steps: + self.cnt = 0 + else: + for block_id, block in enumerate(self.transformer_blocks): + hidden_states, encoder_hidden_states = self.gradient_checkpointing_method( + block, + hidden_states, + encoder_hidden_states, + temb, + attention_mask, + rope_freqs + ) + + for block_id, block in enumerate(self.single_transformer_blocks): + hidden_states, encoder_hidden_states = self.gradient_checkpointing_method( + block, + hidden_states, + encoder_hidden_states, + temb, + attention_mask, + rope_freqs + ) + + hidden_states = self.gradient_checkpointing_method(self.norm_out, hidden_states, temb) + + hidden_states = hidden_states[:, -original_context_length:, :] + + if self.high_quality_fp32_output_for_inference: + hidden_states = hidden_states.to(dtype=torch.float32) + if self.proj_out.weight.dtype != torch.float32: + self.proj_out.to(dtype=torch.float32) + + hidden_states = self.gradient_checkpointing_method(self.proj_out, hidden_states) + + hidden_states = einops.rearrange(hidden_states, 'b (t h w) (c pt ph pw) -> b c (t pt) (h ph) (w pw)', + t=post_patch_num_frames, h=post_patch_height, w=post_patch_width, + pt=p_t, ph=p, pw=p) + + if return_dict: + if self.verbose_messaging: + print("MagCache Forward Exit.") + + return Transformer2DModelOutput(sample=hidden_states) + + if self.verbose_messaging: + print("MagCache Forward Exit.") + + return hidden_states, + def forward( self, hidden_states, timestep, encoder_hidden_states, encoder_attention_mask, pooled_projections, guidance, @@ -1014,3 +1199,22 @@ class HunyuanVideoTransformer3DModel(ModelMixin, ConfigMixin, PeftAdapterMixin, return Transformer2DModelOutput(sample=hidden_states) return hidden_states, + + # def forward( + # self, + # hidden_states, timestep, encoder_hidden_states, encoder_attention_mask, pooled_projections, guidance, + # latent_indices=None, + # clean_latents=None, clean_latent_indices=None, + # clean_latents_2x=None, clean_latent_2x_indices=None, + # clean_latents_4x=None, clean_latent_4x_indices=None, + # image_embeddings=None, + # attention_kwargs=None, return_dict=True + # ): + # if not self.enable_magcache: + # return self.orig_forward(hidden_states, timestep, encoder_hidden_states, encoder_attention_mask, pooled_projections, guidance, latent_indices=latent_indices, clean_latents=clean_latents, clean_latent_indices=clean_latent_indices, + # clean_latents_2x=clean_latents_2x, clean_latent_2x_indices=clean_latent_2x_indices, clean_latents_4x=clean_latents_4x, clean_latent_4x_indices=clean_latents_4x, image_embeddings=image_embeddings, + # attention_kwargs=attention_kwargs, return_dict=return_dict) + # + # return self.magcache_framepack_forward(hidden_states, timestep, encoder_hidden_states, encoder_attention_mask, pooled_projections, guidance, latent_indices=latent_indices, clean_latents=clean_latents, clean_latent_indices=clean_latent_indices, + # clean_latents_2x=clean_latents_2x, clean_latent_2x_indices=clean_latent_2x_indices, clean_latents_4x=clean_latents_4x, clean_latent_4x_indices=clean_latents_4x, image_embeddings=image_embeddings, + # attention_kwargs=attention_kwargs, return_dict=return_dict) diff --git a/nodes.py b/nodes.py index 1275d5d..ed5c26f 100644 --- a/nodes.py +++ b/nodes.py @@ -61,12 +61,23 @@ from .diffusers_helper.k_diffusion_hunyuan import sample_hunyuan, sample_hunyuan from .diffusers_helper.utils import crop_or_pad_yield_mask, soft_append_bcthw import math +import threading +import copy + +import safetensors +import einops + import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) ALLOWED_VIDEO_EXT = ('mp4', 'flv', 'mov', 'avi', 'mpg', 'webm', 'mkv') +class VideoGenerationType(Enum): + T2V = "t2v" + I2V = "i2v" + V2V = "v2v" + def get_offload_device(): return torch.device("cpu") @@ -86,7 +97,6 @@ def pil2tensorSwap(image: Union[Image.Image, List[Image.Image]], device=None) -> return torch.cat([pil2tensor(img) for img in image], dim=0) new_tensor = torch.cat([pil2tensor(img, device=device) for img in image], dim=0) - # new_tensor = new_tensor.to(get_offload_device()) return new_tensor @@ -94,7 +104,6 @@ def pil2tensorSwap(image: Union[Image.Image, List[Image.Image]], device=None) -> return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) new_tensor = torch.from_numpy(np.array(image).astype(np.float32) / 255.0).to(device).unsqueeze(0) - # new_tensor = new_tensor.to(get_offload_device()) return new_tensor @@ -122,6 +131,14 @@ def get_lora_list(): return templist +def get_default_gen_output_path(): + default_path = "{}\\MyLoras\\new_lora_hy.safetensors".format(folder_paths.output_directory) + return default_path + +def get_default_pt_st_output_folder(): + default_path = "{}\\pt_conversions".format(folder_paths.output_directory) + return default_path + def get_default_output_path(): default_path = "{}\\MergedHunyuanLoras\\new_lora_hy.safetensors".format(folder_paths.output_directory) return default_path @@ -130,6 +147,10 @@ def get_default_output_folder(): default_folder = "{}\\MergedHunyuanLoras".format(folder_paths.output_directory) return default_folder +def get_default_xl_output_folder(): + default_folder = "{}\\MergedXLLoras".format(folder_paths.output_directory) + return default_folder + def get_default_wan_output_path(): default_path = "{}\\MergedWanLoras\\new_lora_wan.safetensors".format(folder_paths.output_directory) return default_path @@ -247,6 +268,123 @@ def partial_decode_tiled(vae, latents, tile_size, overlap=64, temporal_size=64, return images +def convert_key_format(key: str) -> str: + """Standardize LoRA key format by removing prefixes.""" + prefixes = ["diffusion_model.", "transformer."] + for prefix in prefixes: + if key.startswith(prefix): + key = key[len(prefix):] + break + return key + +def filter_lora_keys(lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: + """Filter LoRA weights based on block type.""" + if blocks_type == "all": + return lora + filtered_lora = {} + for key, value in lora.items(): + base_key = convert_key_format(key) + if blocks_type in base_key: + filtered_lora[key] = value + return filtered_lora + +def check_for_musubi(lora: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """Checks for and converts from Musubi Tuner format which supports Network Alpha and uses different naming. Largely copied from that project""" + prefix = "lora_unet_" + musubi = False + lora_alphas = {} + for key, value in lora.items(): + if key.startswith(prefix): + lora_name = key.split(".", 1)[0] # before first dot + if lora_name not in lora_alphas and "alpha" in key: + lora_alphas[lora_name] = value + musubi = True + if musubi: + print("Loading Musubi Tuner format LoRA...") + converted_lora = {} + for key, weight in lora.items(): + if key.startswith(prefix): + if "alpha" in key: + continue + lora_name = key.split(".", 1)[0] # before first dot + # HunyuanVideo lora name to module name: ugly but works + module_name = lora_name[len(prefix) :] # remove "lora_unet_" + module_name = module_name.replace("_", ".") # replace "_" with "." + module_name = module_name.replace("double.blocks.", "double_blocks.") # fix double blocks + module_name = module_name.replace("single.blocks.", "single_blocks.") # fix single blocks + module_name = module_name.replace("img.", "img_") # fix img + module_name = module_name.replace("txt.", "txt_") # fix txt + module_name = module_name.replace("attn.", "attn_") # fix attn + diffusers_prefix = "diffusion_model" + if "lora_down" in key: + new_key = f"{diffusers_prefix}.{module_name}.lora_A.weight" + dim = weight.shape[0] + elif "lora_up" in key: + new_key = f"{diffusers_prefix}.{module_name}.lora_B.weight" + dim = weight.shape[1] + else: + print(f"unexpected key: {key} in Musubi LoRA format") + continue + # scale weight by alpha + if lora_name in lora_alphas: + # we scale both down and up, so scale is sqrt + scale = lora_alphas[lora_name] / dim + scale = scale.sqrt() + weight = weight * scale + else: + print(f"missing alpha for {lora_name}") + + converted_lora[new_key] = weight + return converted_lora + else: + print("Loading Diffusers format LoRA...") + return lora + +def convert_lora(lora, convert_to="diffusion_model"): + # Load the LoRA weights + temp_lora = check_for_musubi(lora) + + new_lora = {} + + for key in lora.keys(): + if convert_to in key: + new_lora = lora + break + + if key.startswith("transformer.") and (convert_to == "diffusion_model"): + new_key = key.replace("transformer.", "diffusion_model.") + new_lora[new_key] = lora[key] + continue + + if key.startswith("diffusion_model.") and (convert_to == "transformer"): + new_key = key.replace("diffusion_model.", "transformer.") + new_lora[new_key] = lora[key] + continue + + if key.startswith("lora_unet_"): + new_key = key.replace("lora_unet_", "{}.".format(convert_to)) + + if "double" in new_key: + new_key = new_key.replace("double_blocks_", "double_blocks.") + new_key = new_key.replace("_img_attn", ".img_attn") + new_key = new_key.replace("_img_mlp", ".img_mlp") + new_key = new_key.replace("_txt_attn", ".txt_attn") + new_key = new_key.replace("_txt_mlp", ".txt_mlp") + new_key = new_key.replace(".lora_up.", ".lora_A.") + new_key = new_key.replace(".lora_down.", ".lora_B.") + + continue + + if "single" in new_key: + new_key = new_key.replace("single_blocks_", "single_blocks.") + new_key = new_key.replace("_linear", ".linear") + new_key = new_key.replace(".lora_up.", ".lora_A.") + new_key = new_key.replace(".lora_down.", ".lora_B.") + + continue + + return new_lora + class HyVideoModel(comfy.model_base.BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -259,7 +397,6 @@ class HyVideoModel(comfy.model_base.BaseModel): def __setitem__(self, k, v): self.pipeline[k] = v - class HyVideoModelConfig: def __init__(self, dtype): self.unet_config = {} @@ -271,32 +408,6 @@ class HyVideoModelConfig: self.memory_usage_factor = 2.0 self.unet_config["disable_unet_model_creation"] = True -# def partial_decode_tiled(vae, latents, tile_size, overlap=64, temporal_size=64, temporal_overlap=8, unload_after=True): -# if tile_size < overlap * 4: -# overlap = tile_size // 4 -# if temporal_size < temporal_overlap * 2: -# temporal_overlap = temporal_overlap // 2 -# temporal_compression = vae.temporal_compression_decode() -# if temporal_compression is not None: -# temporal_size = max(2, temporal_size // temporal_compression) -# temporal_overlap = max(1, min(temporal_size // 2, temporal_overlap // temporal_compression)) -# else: -# temporal_size = None -# temporal_overlap = None -# -# compression = vae.spacial_compression_decode() -# images = vae.decode_tiled(latents, tile_x=tile_size // compression, tile_y=tile_size // compression, overlap=overlap // compression, tile_t=temporal_size, overlap_t=temporal_overlap) -# if len(images.shape) == 5: #Combine batches -# images = images.reshape(-1, images.shape[-3], images.shape[-2], images.shape[-1]) -# -# if unload_after: -# mm.unload_all_models() -# mm.soft_empty_cache() -# gc.collect() -# time.sleep(1) -# -# return images - def warped_prepare_noise(latent_image, seed, generator=None): """ creates random noise given a latent image and a seed. @@ -351,29 +462,9 @@ class WarpedHunyuanLoraMerge: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - """Filter LoRA weights based on block type.""" - if blocks_type == "all": - return lora - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - if blocks_type in base_key: - filtered_lora[key] = value - return filtered_lora - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -387,10 +478,7 @@ class WarpedHunyuanLoraMerge: # Load the LoRA weights lora_weights = utils.load_torch_file(lora_path) - # Filter the LoRA weights based on the block type - filtered_lora = self.filter_lora_keys(lora_weights, blocks_type) - - return lora_weights, filtered_lora + return lora_weights def merge_multiple_loras(self, save_path, lora_1, strength_1, blocks_type_1, lora_2, strength_2, blocks_type_2, save_metadata=True): """Load and apply multiple LoRA models.""" @@ -401,12 +489,16 @@ class WarpedHunyuanLoraMerge: if lora_1 != "None" and strength_1 != 0: # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_1, 1.0, blocks_type_1) + lora_weights = self.load_lora(lora_1, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_1) temp_loras["1"] = {"lora_weights": lora_weights, "strength": strength_1, "filtered_lora": filtered_lora} if lora_2 != "None" and strength_2 != 0: # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_2, 1.0, blocks_type_2) + lora_weights = self.load_lora(lora_2, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_2) temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength_2, "filtered_lora": filtered_lora} loras = {} @@ -426,10 +518,6 @@ class WarpedHunyuanLoraMerge: new_lora[key] = None print("Lora: {} | Key: {} | Shape: {}".format(lora_key, key, loras[lora_key]["lora_weights"][key].shape)) - # print_it = True - # - # i = 0 - # Merge The Weighted Key Weights for key in new_lora.keys(): for lora_key in loras.keys(): @@ -437,11 +525,6 @@ class WarpedHunyuanLoraMerge: if not new_lora[key] is None: temp_weights = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) - # if print_it: - # print("\n--------------------------------------------------------------------") - # print("Before: New Lora Shape: {} | Temp Weights Shape: {}".format(new_lora[key].shape, temp_weights.shape)) - # print("--------------------------------------------------------------------\n") - if new_lora[key].shape[0] < new_lora[key].shape[1]: if temp_weights.shape[0] < new_lora[key].shape[0]: temp_weights = temp_weights.clone().detach() @@ -474,20 +557,7 @@ class WarpedHunyuanLoraMerge: new_lora[key] = padding try: - # new_lora[key] = ((new_lora[key] + temp_weights) / 2.0) new_lora[key] = torch.add(new_lora[key], temp_weights) - - # if print_it: - # if (i + 1) >= 10: - # print_it = False - # - # i += 1 - # print("\n--------------------------------------------------------------------") - # print("After: New Lora Shape: {}".format(new_lora[key].shape)) - # print("--------------------------------------------------------------------\n") - # print("\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") - # print("After: New Lora Key Tensor: {}".format(new_lora[key])) - # print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n") except Exception as e: raise(e) else: @@ -532,29 +602,9 @@ class WarpedHunyuanMultiLoraMerge: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - """Filter LoRA weights based on block type.""" - if blocks_type == "all": - return lora - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - if blocks_type in base_key: - filtered_lora[key] = value - return filtered_lora - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -568,10 +618,7 @@ class WarpedHunyuanMultiLoraMerge: # Load the LoRA weights lora_weights = utils.load_torch_file(lora_path) - # Filter the LoRA weights based on the block type - filtered_lora = self.filter_lora_keys(lora_weights, blocks_type) - - return lora_weights, filtered_lora + return lora_weights def merge_multiple_loras(self, save_path, lora_1, strength_1, blocks_type_1, lora_2, strength_2, blocks_type_2, lora_3, strength_3, blocks_type_3, lora_4, strength_4, blocks_type_4, save_metadata=True): temp_loras = {} @@ -581,22 +628,30 @@ class WarpedHunyuanMultiLoraMerge: if lora_1 != "None" and strength_1 != 0: # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_1, 1.0, blocks_type_1) + lora_weights = self.load_lora(lora_1, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_1) temp_loras["1"] = {"lora_weights": lora_weights, "strength": strength_1, "filtered_lora": filtered_lora} if lora_2 != "None" and strength_2 != 0: # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_2, 1.0, blocks_type_2) + lora_weights = self.load_lora(lora_2, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_2) temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength_2, "filtered_lora": filtered_lora} if lora_3 != "None" and strength_3 != 0: # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_3, 1.0, blocks_type_3) + lora_weights = self.load_lora(lora_3, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_3) temp_loras["3"] = {"lora_weights": lora_weights, "strength": strength_3, "filtered_lora": filtered_lora} if lora_4 != "None" and strength_4 != 0: # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_4, 1.0, blocks_type_4) + lora_weights = self.load_lora(lora_4, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_4) temp_loras["4"] = {"lora_weights": lora_weights, "strength": strength_4, "filtered_lora": filtered_lora} loras = {} @@ -616,10 +671,6 @@ class WarpedHunyuanMultiLoraMerge: new_lora[key] = None print("Lora: {} | Key: {}".format(lora_key, key)) - # print_it = True - # - # i = 0 - # Merge The Weighted Key Weights for key in new_lora.keys(): for lora_key in loras.keys(): @@ -627,11 +678,6 @@ class WarpedHunyuanMultiLoraMerge: if not new_lora[key] is None: temp_weights = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) - # if print_it: - # print("\n--------------------------------------------------------------------") - # print("Before: New Lora Shape: {} | Temp Weights Shape: {}".format(new_lora[key].shape, temp_weights.shape)) - # print("--------------------------------------------------------------------\n") - if new_lora[key].shape[0] < new_lora[key].shape[1]: if temp_weights.shape[0] < new_lora[key].shape[0]: temp_weights = temp_weights.clone().detach() @@ -690,10 +736,8 @@ class WarpedHunyuanLoraAvgMerge: "required": { "save_path": ("STRING", {"default": get_default_output_path()}), "lora_1": (['None'] + get_lora_list(),), - # "strength_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.01}), "blocks_type_1": (["all", "single_blocks", "double_blocks"], {"default": "all"}), "lora_2": (['None'] + get_lora_list(),), - # "strength_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.01}), "blocks_type_2": (["all", "single_blocks", "double_blocks"], {"default": "all"}), "save_metadata": ("BOOLEAN", {"default": True}), }, @@ -703,29 +747,9 @@ class WarpedHunyuanLoraAvgMerge: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - """Filter LoRA weights based on block type.""" - if blocks_type == "all": - return lora - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - if blocks_type in base_key: - filtered_lora[key] = value - return filtered_lora - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -739,10 +763,7 @@ class WarpedHunyuanLoraAvgMerge: # Load the LoRA weights lora_weights = utils.load_torch_file(lora_path) - # Filter the LoRA weights based on the block type - filtered_lora = self.filter_lora_keys(lora_weights, blocks_type) - - return lora_weights, filtered_lora + return lora_weights def merge_multiple_loras(self, save_path, lora_1, blocks_type_1, lora_2, blocks_type_2, save_metadata=True): """Load and apply multiple LoRA models.""" @@ -754,12 +775,16 @@ class WarpedHunyuanLoraAvgMerge: if lora_1 != "None": # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_1, 1.0, blocks_type_1) + lora_weights = self.load_lora(lora_1, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_1) temp_loras["1"] = {"lora_weights": lora_weights, "strength": strength, "filtered_lora": filtered_lora} if lora_2 != "None": # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_2, 1.0, blocks_type_2) + lora_weights = self.load_lora(lora_2, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_2) temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength, "filtered_lora": filtered_lora} loras = {} @@ -860,29 +885,9 @@ class WarpedHunyuanMultiLoraAvgMerge: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - """Filter LoRA weights based on block type.""" - if blocks_type == "all": - return lora - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - if blocks_type in base_key: - filtered_lora[key] = value - return filtered_lora - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -896,10 +901,7 @@ class WarpedHunyuanMultiLoraAvgMerge: # Load the LoRA weights lora_weights = utils.load_torch_file(lora_path) - # Filter the LoRA weights based on the block type - filtered_lora = self.filter_lora_keys(lora_weights, blocks_type) - - return lora_weights, filtered_lora + return lora_weights def merge_multiple_loras(self, save_path, lora_1, blocks_type_1, lora_2, blocks_type_2, lora_3, blocks_type_3, lora_4, blocks_type_4, save_metadata=True): strength = 1.0000 @@ -910,22 +912,30 @@ class WarpedHunyuanMultiLoraAvgMerge: if lora_1 != "None": # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_1, 1.0, blocks_type_1) + lora_weights = self.load_lora(lora_1, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_1) temp_loras["1"] = {"lora_weights": lora_weights, "strength": strength, "filtered_lora": filtered_lora} if lora_2 != "None": # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_2, 1.0, blocks_type_2) + lora_weights = self.load_lora(lora_2, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_2) temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength, "filtered_lora": filtered_lora} if lora_3 != "None": # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_3, 1.0, blocks_type_3) + lora_weights = self.load_lora(lora_3, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_3) temp_loras["3"] = {"lora_weights": lora_weights, "strength": strength, "filtered_lora": filtered_lora} if lora_4 != "None": # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(lora_4, 1.0, blocks_type_4) + lora_weights = self.load_lora(lora_4, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type_4) temp_loras["4"] = {"lora_weights": lora_weights, "strength": strength, "filtered_lora": filtered_lora} loras = {} @@ -1031,7 +1041,7 @@ class WarpedHunyuanMultiLoraMixer: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." def convert_key_format(self, key: str) -> str: @@ -1134,48 +1144,56 @@ class WarpedHunyuanMultiLoraMixer: print(lora_1) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_1, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["1"] = {"lora_weights": lora_weights} if lora_2 != "None": print(lora_2) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_2, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["2"] = {"lora_weights": lora_weights} if lora_3 != "None": print(lora_3) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_3, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["3"] = {"lora_weights": lora_weights} if lora_4 != "None": print(lora_4) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_4, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["4"] = {"lora_weights": lora_weights} if lora_5 != "None": print(lora_5) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_5, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["5"] = {"lora_weights": lora_weights} if lora_6 != "None": print(lora_6) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_6, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["6"] = {"lora_weights": lora_weights} if lora_7 != "None": print(lora_7) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_7, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["7"] = {"lora_weights": lora_weights} if lora_8 != "None": print(lora_8) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_8, 1.0, "all") + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") temp_loras["8"] = {"lora_weights": lora_weights} loras = {} @@ -1243,6 +1261,302 @@ class WarpedHunyuanMultiLoraMixer: return {"ui": {"tags": ["save_message"]}} +# class WarpedHunyuanMultiLoraMixerExt: +# @classmethod +# def INPUT_TYPES(s): +# return { +# "required": { +# "save_folder": ("STRING", {"default": get_default_output_folder()}), +# "model_prefix": ("STRING", {"default": "new_model_hy"}), +# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), +# "num_output": ("INT", {"default": 1, "min": 1, "max": 100}), +# "lora_1": (['None'] + get_lora_list(),), +# "strength_1": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_2": (['None'] + get_lora_list(),), +# "strength_2": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_3": (['None'] + get_lora_list(),), +# "strength_3": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_4": (['None'] + get_lora_list(),), +# "strength_4": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_5": (['None'] + get_lora_list(),), +# "strength_5": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_6": (['None'] + get_lora_list(),), +# "strength_6": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_7": (['None'] + get_lora_list(),), +# "strength_7": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "lora_8": (['None'] + get_lora_list(),), +# "strength_8": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), +# "save_metadata": ("BOOLEAN", {"default": True}), +# }, +# } +# +# RETURN_TYPES = () +# OUTPUT_NODE = True +# OUTPUT_IS_LIST = (True,) +# FUNCTION = "merge_multiple_loras" +# CATEGORY = "Warped/Hunyuan/Merge" +# DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." +# +# def convert_key_format(self, key: str) -> str: +# """Standardize LoRA key format by removing prefixes.""" +# prefixes = ["diffusion_model.", "transformer."] +# for prefix in prefixes: +# if key.startswith(prefix): +# key = key[len(prefix):] +# break +# return key +# +# def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: +# """Load and filter a single LoRA model.""" +# if not lora_name or strength == 0: +# return {}, {} +# +# # Get the full path to the LoRA file +# lora_path = folder_paths.get_full_path("loras", lora_name) +# if not os.path.exists(lora_path): +# raise ValueError(f"LoRA file not found: {lora_path}") +# +# # Load the LoRA weights +# lora_weights = utils.load_torch_file(lora_path) +# +# return lora_weights +# +# def get_random_key(self, keys): +# if len(keys) > 0: +# random_key = random.randint(0, len(keys) - 1) +# # print("Keys: {} | Random Key: {}".format(random_key)) +# return keys[random_key] +# +# return -1 +# +# def get_mixtures(self, seed, num_output, lora_keys, block_types): +# random.seed(seed) +# mixtures = {} +# +# for i in range(num_output): +# mixtures["{}".format(i + 1)] = {} +# +# single_block_loras = [] +# double_block_loras = [] +# +# for key in lora_keys: +# for mixture_key in mixtures.keys(): +# mixtures[mixture_key][key] = {"single": [], "double": []} +# +# if block_types[key]["has_single_blocks"]: +# single_block_loras.append(int(key)) +# +# if block_types[key]["has_double_blocks"]: +# double_block_loras.append(int(key)) +# +# for mixture_key in mixtures.keys(): +# if len(single_block_loras) > 0: +# for j in range(40): +# random_key = self.get_random_key(single_block_loras) +# temp_key = "{}".format(random_key) +# mixtures[mixture_key][temp_key]["single"].append(j) +# +# if len(double_block_loras) > 0: +# for j in range(20): +# random_key = self.get_random_key(double_block_loras) +# temp_key = "{}".format(random_key) +# mixtures[mixture_key][temp_key]["double"].append(j) +# +# i += 1 +# +# print("\nMixtures\n") +# +# block_metadata = "" +# +# for mixture_key in mixtures.keys(): +# for key in mixtures[mixture_key]: +# print("{} | {}: {}".format(mixture_key, key, mixtures[mixture_key][key])) +# +# if len(block_metadata) > 0: +# block_metadata = "{} | {}".format(block_metadata, "{}: {}: {}".format(mixture_key, key, mixtures[mixture_key][key])) +# else: +# block_metadata = "{}: {}: {}".format(mixture_key, key, mixtures[mixture_key][key]) +# +# print("\n") +# +# return mixtures, block_metadata +# +# def determine_lora_block_types(self, loras): +# block_types = {} +# +# for lora_key in loras.keys(): +# block_types[lora_key] = { "has_single_blocks": False, "has_double_blocks": False } +# +# for key in loras[lora_key]["lora_weights"].keys(): +# if "single_blocks" in key: +# block_types[lora_key]["has_single_blocks"] = True +# elif "double_blocks" in key: +# block_types[lora_key]["has_double_blocks"] = True +# +# if block_types[lora_key]["has_single_blocks"] and block_types[lora_key]["has_double_blocks"]: +# break +# +# return block_types +# +# def convert_lora_dimensions(self, max_dimension, lora): +# new_lora = {} +# +# for key in lora.keys(): +# temp_weights = lora[key] +# +# if temp_weights.shape[0] < temp_weights.shape[1]: +# if temp_weights.shape[0] < max_dimension: +# padding = torch.zeros([max_dimension, temp_weights.shape[1]]) +# padding[:temp_weights.shape[0],:] = temp_weights +# new_lora[key] = padding +# else: +# new_lora[key] = temp_weights +# else: +# if temp_weights.shape[1] < max_dimension: +# padding = torch.zeros([temp_weights.shape[0], max_dimension]) +# padding[:,:temp_weights.shape[1]] = temp_weights +# new_lora[key] = padding +# else: +# new_lora[key] = temp_weights +# lora = None +# +# return new_lora +# +# def merge_multiple_loras(self, save_folder, model_prefix, seed, num_output, lora_1, strength_1, lora_2, strength_2, lora_3, strength_3, lora_4, strength_4, +# lora_5, strength_5, lora_6, strength_6, lora_7, strength_7, lora_8, strength_8, save_metadata=True): +# print("Save_folder: {}".format(save_folder)) +# os.makedirs(save_folder, exist_ok = True) +# +# temp_loras = {} +# metadata = {"loras": "{} and {} and {} and {} and {} and {} and {} and {}".format(lora_1, lora_2, lora_3, lora_4, lora_5, lora_6, lora_7, lora_8)} +# metadata["strengths"] = "{} and {} and {} and {} and {} and {} and {} and {}".format(strength_1, strength_2, strength_3, strength_4, strength_5, strength_6, strength_7, strength_8) +# metadata["seed"] = "{}".format(seed) +# metadata["num_output"] = "{}".format(num_output) +# +# if (lora_1 != "None") and (strength_1 > 0.0): +# print(lora_1) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_1, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["1"] = {"lora_weights": lora_weights, "strength": strength_1} +# +# if (lora_2 != "None") and (strength_2 > 0.0): +# print(lora_2) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_2, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength_2} +# +# if (lora_3 != "None") and (strength_3 > 0.0): +# print(lora_3) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_3, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["3"] = {"lora_weights": lora_weights, "strength": strength_3} +# +# if (lora_4 != "None") and (strength_4 > 0.0): +# print(lora_4) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_4, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["4"] = {"lora_weights": lora_weights, "strength": strength_4} +# +# if (lora_5 != "None") and (strength_5 > 0.0): +# print(lora_5) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_5, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["5"] = {"lora_weights": lora_weights, "strength": strength_5} +# +# if (lora_6 != "None") and (strength_6 > 0.0): +# print(lora_6) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_6, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["6"] = {"lora_weights": lora_weights, "strength": strength_6} +# +# if (lora_7 != "None") and (strength_7 > 0.0): +# print(lora_7) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_7, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["7"] = {"lora_weights": lora_weights, "strength": strength_7} +# +# if (lora_8 != "None") and (strength_8 > 0.0): +# print(lora_8) +# # Load and filter the LoRA weights +# lora_weights = self.load_lora(lora_8, 1.0, "all") +# lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") +# temp_loras["8"] = {"lora_weights": lora_weights, "strength": strength_8} +# +# loras = {} +# max_dimension = 0 +# +# for lora_key in temp_loras.keys(): +# # print(lora_key) +# loras[lora_key] = {"lora_weights": {}, "strength": temp_loras[lora_key]["strength"]} +# +# for key in temp_loras[lora_key]["lora_weights"].keys(): +# new_key = key.replace("transformer.", "diffusion_model.") +# loras[lora_key]["lora_weights"][new_key] = temp_loras[lora_key]["lora_weights"][key] +# +# temp_dimension = min(loras[lora_key]["lora_weights"][new_key].shape[0], loras[lora_key]["lora_weights"][new_key].shape[1]) +# +# if temp_dimension > max_dimension: +# max_dimension = temp_dimension +# +# block_types = self.determine_lora_block_types(loras) +# merge_mixtures, block_metadata = self.get_mixtures(seed, num_output, loras.keys(), block_types) +# +# metadata["max_dimension"] = "{}".format(max_dimension) +# metadata["block_types"] = "{}".format(block_types) +# +# print("Max Dimension: {}".format(max_dimension)) +# +# save_message = "" +# +# for mixture_key in merge_mixtures: +# new_lora = {} +# output_filename = os.path.join(save_folder, "{}_{:05}.safetensors".format(model_prefix, int(mixture_key))) +# +# metadata["merge_mixture"] = "{}".format(merge_mixtures[mixture_key]) +# # metadata["block_metadata"] = "{}".format(block_metadata[int(mixture_key)]) +# +# for lora_key in loras.keys(): +# mixture_single_blocks = merge_mixtures[mixture_key][lora_key]["single"] +# mixture_double_blocks = merge_mixtures[mixture_key][lora_key]["double"] +# +# for key in loras[lora_key]["lora_weights"].keys(): +# temp_strings = str(key).split('.') +# temp_block_num = int(temp_strings[2]) +# +# if temp_strings[1] == "single_blocks": +# if temp_block_num in mixture_single_blocks: +# new_lora[key] = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) +# continue +# +# if temp_strings[1] == "double_blocks": +# if temp_block_num in mixture_double_blocks: +# new_lora[key] = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) +# +# new_lora = self.convert_lora_dimensions(max_dimension, new_lora) +# +# if not save_metadata: +# metadata = None +# +# print("Saving Model To: {}...".format(output_filename)) +# utils.save_torch_file(new_lora, output_filename, metadata=metadata) +# print("Saving Model To: {}...Done.".format(output_filename)) +# +# save_message = "{}\n{}".format(save_message, "Weights Saved To: {}".format(output_filename)) +# +# new_lora = None +# mm.soft_empty_cache() +# gc.collect() +# time.sleep(1) +# +# return {"ui": {"tags": ["save_message"]}} + class WarpedHunyuanMultiLoraMixerExt: @classmethod def INPUT_TYPES(s): @@ -1269,6 +1583,8 @@ class WarpedHunyuanMultiLoraMixerExt: "lora_8": (['None'] + get_lora_list(),), "strength_8": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01}), "save_metadata": ("BOOLEAN", {"default": True}), + "convert_to": (["diffusion_model", "transformer"], {"default": "diffusion_model"}), + "max_dimension": ([32, 64, 128], {"default": 32}), }, } @@ -1276,18 +1592,9 @@ class WarpedHunyuanMultiLoraMixerExt: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -1301,6 +1608,8 @@ class WarpedHunyuanMultiLoraMixerExt: # Load the LoRA weights lora_weights = utils.load_torch_file(lora_path) + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + return lora_weights def get_random_key(self, keys): @@ -1387,25 +1696,29 @@ class WarpedHunyuanMultiLoraMixerExt: temp_weights = lora[key] if temp_weights.shape[0] < temp_weights.shape[1]: - if temp_weights.shape[0] < max_dimension: - padding = torch.zeros([max_dimension, temp_weights.shape[1]]) + padding = torch.zeros([max_dimension, temp_weights.shape[1]]) + + if temp_weights.shape[0] <= max_dimension: padding[:temp_weights.shape[0],:] = temp_weights new_lora[key] = padding else: - new_lora[key] = temp_weights + padding[:max_dimension,:] = temp_weights[:max_dimension,:] + new_lora[key] = padding else: - if temp_weights.shape[1] < max_dimension: - padding = torch.zeros([temp_weights.shape[0], max_dimension]) + padding = torch.zeros([temp_weights.shape[0], max_dimension]) + + if temp_weights.shape[1] <= max_dimension: padding[:,:temp_weights.shape[1]] = temp_weights new_lora[key] = padding else: - new_lora[key] = temp_weights + padding[:,:max_dimension] = temp_weights[:,:max_dimension] + new_lora[key] = padding lora = None return new_lora def merge_multiple_loras(self, save_folder, model_prefix, seed, num_output, lora_1, strength_1, lora_2, strength_2, lora_3, strength_3, lora_4, strength_4, - lora_5, strength_5, lora_6, strength_6, lora_7, strength_7, lora_8, strength_8, save_metadata=True): + lora_5, strength_5, lora_6, strength_6, lora_7, strength_7, lora_8, strength_8, save_metadata=True, convert_to="diffusion_model", max_dimension=32): print("Save_folder: {}".format(save_folder)) os.makedirs(save_folder, exist_ok = True) @@ -1421,64 +1734,105 @@ class WarpedHunyuanMultiLoraMixerExt: lora_weights = self.load_lora(lora_1, 1.0, "all") temp_loras["1"] = {"lora_weights": lora_weights, "strength": strength_1} + for key in lora_weights.keys(): + print("LORA 1: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_2 != "None") and (strength_2 > 0.0): print(lora_2) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_2, 1.0, "all") temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength_2} + for key in lora_weights.keys(): + print("LORA 2: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_3 != "None") and (strength_3 > 0.0): print(lora_3) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_3, 1.0, "all") temp_loras["3"] = {"lora_weights": lora_weights, "strength": strength_3} + for key in lora_weights.keys(): + print("LORA 3: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_4 != "None") and (strength_4 > 0.0): print(lora_4) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_4, 1.0, "all") temp_loras["4"] = {"lora_weights": lora_weights, "strength": strength_4} + for key in lora_weights.keys(): + print("LORA 4: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_5 != "None") and (strength_5 > 0.0): print(lora_5) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_5, 1.0, "all") temp_loras["5"] = {"lora_weights": lora_weights, "strength": strength_5} + for key in lora_weights.keys(): + print("LORA 5: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_6 != "None") and (strength_6 > 0.0): print(lora_6) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_6, 1.0, "all") temp_loras["6"] = {"lora_weights": lora_weights, "strength": strength_6} + for key in lora_weights.keys(): + print("LORA 6: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_7 != "None") and (strength_7 > 0.0): print(lora_7) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_7, 1.0, "all") temp_loras["7"] = {"lora_weights": lora_weights, "strength": strength_7} + for key in lora_weights.keys(): + print("LORA 7: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + if (lora_8 != "None") and (strength_8 > 0.0): print(lora_8) # Load and filter the LoRA weights lora_weights = self.load_lora(lora_8, 1.0, "all") temp_loras["8"] = {"lora_weights": lora_weights, "strength": strength_8} + for key in lora_weights.keys(): + print("LORA 8: {} | Sample Shape: {}".format(key, lora_weights[key].shape)) + break + + lora_weights = None + loras = {} - max_dimension = 0 for lora_key in temp_loras.keys(): - # print(lora_key) loras[lora_key] = {"lora_weights": {}, "strength": temp_loras[lora_key]["strength"]} for key in temp_loras[lora_key]["lora_weights"].keys(): new_key = key.replace("transformer.", "diffusion_model.") loras[lora_key]["lora_weights"][new_key] = temp_loras[lora_key]["lora_weights"][key] - temp_dimension = min(loras[lora_key]["lora_weights"][new_key].shape[0], loras[lora_key]["lora_weights"][new_key].shape[1]) - - if temp_dimension > max_dimension: - max_dimension = temp_dimension - block_types = self.determine_lora_block_types(loras) merge_mixtures, block_metadata = self.get_mixtures(seed, num_output, loras.keys(), block_types) @@ -1553,18 +1907,9 @@ class WarpedWanLoraMerge: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/WanTools" + CATEGORY = "Warped/Wan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -1613,21 +1958,12 @@ class WarpedWanLoraMerge: new_lora[key] = None print("Lora: {} | Key: {} | Shape: {}".format(lora_key, key, loras[lora_key]["lora_weights"][key].shape)) - # print_it = True - # - # i = 0 - # Merge The Weighted Key Weights for key in new_lora.keys(): for lora_key in loras.keys(): if key in loras[lora_key]["lora_weights"].keys(): if not new_lora[key] is None: temp_weights = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) - # - # if print_it: - # print("\n--------------------------------------------------------------------") - # print("Before: New Lora Shape: {} | Temp Weights Shape: {}".format(new_lora[key].shape, temp_weights.shape)) - # print("--------------------------------------------------------------------\n") if new_lora[key].shape[0] < new_lora[key].shape[1]: if temp_weights.shape[0] < new_lora[key].shape[0]: @@ -1661,20 +1997,7 @@ class WarpedWanLoraMerge: new_lora[key] = padding try: - # new_lora[key] = ((new_lora[key] + temp_weights) / 2.0) new_lora[key] = torch.add(new_lora[key], temp_weights) - - # if print_it: - # if (i + 1) >= 10: - # print_it = False - # - # i += 1 - # print("\n--------------------------------------------------------------------") - # print("After: New Lora Shape: {}".format(new_lora[key].shape)) - # print("--------------------------------------------------------------------\n") - # print("\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") - # print("After: New Lora Key Tensor: {}".format(new_lora[key])) - # print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n") except Exception as e: raise(e) else: @@ -1706,7 +2029,7 @@ class WarpedCreateSpecialImageBatch: RETURN_NAMES = ("image", "num_images", ) FUNCTION = "generate" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def generate(self, image, color="#000000", batch_size=1, all_same_image=False): image_color = ImageColor.getrgb(color) @@ -1789,7 +2112,7 @@ class WarpedCreateSpecialImageBatchExt: RETURN_NAMES = ("image", "num_images", ) FUNCTION = "generate" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def generate(self, image, color="#000000", batch_size=1, all_same_image=False, start_image=None): image_color = ImageColor.getrgb(color) @@ -1836,7 +2159,7 @@ class WarpedCreateSpecialImageBatchExp: RETURN_NAMES = ("image", "num_images", ) FUNCTION = "generate" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def generate(self, image, batch_size=1, all_same_image=False): if len(image.shape) < 4: @@ -1876,7 +2199,7 @@ class WarpedCreateEmptyImageBatch: RETURN_NAMES = ("image", "num_images", ) FUNCTION = "generate" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def generate(self, batch_size, width, height): color="#000000" @@ -1918,7 +2241,7 @@ class WarpedCreateEmptyLatentBatch: RETURN_NAMES = ("latents", "num_images", ) FUNCTION = "generate" - CATEGORY = "Warped/Latent" + CATEGORY = "Warped/General/Latent" def generate(self, batch_size, width, height): temp_latent = torch.zeros([1, 16, int(((batch_size - 1) / 4) + 1), int(height // 8), int(width // 8)], dtype=torch.float32, device=self.offload_device) @@ -1962,7 +2285,7 @@ class WarpedSamplerCustomAdv: FUNCTION = "sample" - CATEGORY = "Warped/Sampling" + CATEGORY = "Warped/General/Sampling" def sample(self, image, vae, seed, guider, sampler, sigmas, enc_tile_size, enc_overlap, enc_temporal_size, enc_temporal_overlap, dec_tile_size, dec_overlap, dec_temporal_size, dec_temporal_overlap, skip_frames, noise_scale, scaling_strength=1.0, output_latents=False): @@ -2322,7 +2645,6 @@ class WarpedSamplerCustomAdvLatent: }, "optional": {"scaling_strength": ("FLOAT", {"default": 1.0}), - # "block_swap_args": ("BLOCKSWAPARGS", ), "output_latents": ("BOOLEAN", {"default": False}), } } @@ -2332,7 +2654,7 @@ class WarpedSamplerCustomAdvLatent: FUNCTION = "sample" - CATEGORY = "Warped/Sampling" + CATEGORY = "Warped/General/Sampling" def sample(self, latent, vae, seed, guider, sampler, sigmas, dec_tile_size, dec_overlap, dec_temporal_size, dec_temporal_overlap, skip_frames, noise_scale, scaling_strength=1.0, output_latents=False): @@ -2360,7 +2682,7 @@ class WarpedSamplerCustomAdvLatent: latents = latent["samples"] if len(latents.shape) < 5: - latents = latents.unsqueeze (0) + latents = latents.unsqueeze(0) num_frames = int(((latents.shape[2] - 1) * 4) + 1) @@ -2393,7 +2715,6 @@ class WarpedSamplerCustomAdvLatent: print("WarpedSamplerCustomAdvLatent: Generating {} Frames in {} Latents....".format(num_frames, latents.shape[2])) samples = guider.sample(noise_latents, latents, sampler, sigmas, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=self.seed) - samples = samples.to(mm.intermediate_device()) mm.unload_all_models() @@ -2631,7 +2952,7 @@ class WarpedCreateSpecialImageBatchFromVideo: RETURN_NAMES = ("image_batch", "first_image", "num_frames",) FUNCTION = "generate" - CATEGORY = "Warped/Video" + CATEGORY = "Warped/General/Video" def generate(self, video_path, last_n_frames, num_frames): cap = cv2.VideoCapture(video_path) @@ -2718,7 +3039,7 @@ class WarpedBundleAllVideoImages: RETURN_NAMES = ("image_batch", "first_image", "last_image", "num_frames", "width", "height", "fps", ) FUNCTION = "generate" - CATEGORY = "Warped/Video" + CATEGORY = "Warped/General/Video" def generate(self, video_path, use_gpu=False): if use_gpu: @@ -2812,7 +3133,7 @@ class WarpedImageNoiseAugmentation: RETURN_NAMES = ("image", "seed", ) FUNCTION = "add_noise" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def add_noise(self, image, noise_aug_strength, seed): image_out = augmentation_add_noise(image, noise_aug_strength, seed) @@ -2867,7 +3188,7 @@ class WarpedLeapfusionHunyuanI2V: RETURN_NAMES = ("model", "scale_factor", ) FUNCTION = "patch" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/LeapFusion" def patch(self, model, latent, index, strength, start_percent, end_percent): new_model, scaling_strength = augmentation_patch_model(model, latent, index, strength, start_percent, end_percent) @@ -2893,7 +3214,7 @@ class WarpedSaveAnimatedPng: RETURN_TYPES = () FUNCTION = "save_images" OUTPUT_NODE = True - CATEGORY = "Warped/Image/Animation" + CATEGORY = "Warped/General/Image/Animation" def save_images(self, images, fps, compress_level, png_filename=""): temp_filename = png_filename.split('.') @@ -3072,7 +3393,7 @@ class WarpedBasicGuider: RETURN_TYPES = ("GUIDER",) FUNCTION = "get_guider" - CATEGORY = "Warped/Sampling/Guiders" + CATEGORY = "Warped/General/Sampling/Guiders" def get_guider(self, conditioning): guider = WarpedGuider_Basic() @@ -3110,7 +3431,7 @@ class WarpedUpscaleWithModel: RETURN_NAMES = ("images",) FUNCTION = "upscale" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def upscale(self, upscale_model, images, upscale_by, rescale_method): @@ -3155,7 +3476,7 @@ class WarpedLoadVideosBatch: FUNCTION = "load_batch_videos" - CATEGORY = "Warped/Video" + CATEGORY = "Warped/General/Video" def load_batch_videos(self, path): if not os.path.exists(path): @@ -3265,7 +3586,7 @@ class WarpedBundleVideoImages: RETURN_NAMES = ("image_batch", "first_image", "last_image", "num_frames", "width", "height", "fps", ) FUNCTION = "generate" - CATEGORY = "Warped/Video" + CATEGORY = "Warped/General/Video" def generate(self, video_path, starting_index, num_frames, use_gpu=False): if use_gpu: @@ -3348,6 +3669,144 @@ class WarpedBundleVideoImages: return (batched_images, first_image, last_image, int(batched_images.shape[0]), width, height, float(fps), ) +class WarpedGetImageFromVideo: + @classmethod + def INPUT_TYPES(s): + return {"required": + {"video_path": ("STRING", {"default": ""}), + "image_index": ("INT", {"default": 0}), + }, + } + RETURN_TYPES = ("IMAGE", "INT", "INT", ) + RETURN_NAMES = ("image", "width", "height", ) + FUNCTION = "generate" + + CATEGORY = "Warped/General/Video" + + def generate(self, video_path, image_index): + cap = cv2.VideoCapture(video_path) + + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + print('WarpedGetImageFromVideo: width = %d' % width) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + print('WarpedGetImageFromVideo: height = %d' % height) + length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + print('WarpedGetImageFromVideo: length = %d' % length) + + return_image = None + + if image_index > length: + image_index = length - 1 + + try: + cap.set(cv2.CAP_PROP_POS_FRAMES, image_index) + + # Take each frame + _, frameorig = cap.read() + + color_coverted = cv2.cvtColor(frameorig, cv2.COLOR_BGR2RGB) + + pil_image = Image.fromarray(color_coverted) + return_image = pil2tensorSwap(pil_image, device=get_offload_device()) + + if len(return_image.shape) < 4: + return_image = return_image.unsqueeze(0) + + print("return_image: Shape: {}".format(return_image.shape)) + + frameorig = None + color_coverted = None + pil_image = None + except: + print("WarpedGetImageFromVideo: Exception During Video File Read.") + finally: + cap.release() + + mm.soft_empty_cache() + gc.collect() + time.sleep(1) + + return (return_image, width, height, ) + +class WarpedGetTwoImagesFromVideo: + @classmethod + def INPUT_TYPES(s): + return {"required": + {"video_path": ("STRING", {"default": ""}), + "first_image_index": ("INT", {"default": 0}), + "second_image_index": ("INT", {"default": 1}), + }, + } + RETURN_TYPES = ("IMAGE", "INT", "INT", ) + RETURN_NAMES = ("first_image", "second_image", "width", "height", ) + FUNCTION = "generate" + + CATEGORY = "Warped/General/Video" + + def generate(self, video_path, first_image_index, second_image_index): + cap = cv2.VideoCapture(video_path) + + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + print('WarpedGetImageFromVideo: width = %d' % width) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + print('WarpedGetImageFromVideo: height = %d' % height) + length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + print('WarpedGetImageFromVideo: length = %d' % length) + + return_image_one = None + return_image_two = None + + if first_image_index > length: + first_image_index = 0 + + if second_image_index > length: + second_image_index = length - 1 + + try: + cap.set(cv2.CAP_PROP_POS_FRAMES, first_image_index) + + # Take each frame + _, frameorig = cap.read() + + color_coverted = cv2.cvtColor(frameorig, cv2.COLOR_BGR2RGB) + + pil_image = Image.fromarray(color_coverted) + return_image_one = pil2tensorSwap(pil_image, device=get_offload_device()) + + if len(return_image_one.shape) < 4: + return_image_one = return_image_one.unsqueeze(0) + + print("return_image_one: Shape: {}".format(return_image_one.shape)) + + cap.set(cv2.CAP_PROP_POS_FRAMES, second_image_index) + + # Take each frame + _, frameorig = cap.read() + + color_coverted = cv2.cvtColor(frameorig, cv2.COLOR_BGR2RGB) + + pil_image = Image.fromarray(color_coverted) + return_image_two = pil2tensorSwap(pil_image, device=get_offload_device()) + + if len(return_image_two.shape) < 4: + return_image_two = return_image_two.unsqueeze(0) + + print("return_image_two: Shape: {}".format(return_image_two.shape)) + + frameorig = None + color_coverted = None + pil_image = None + except: + print("WarpedGetImageFromVideo: Exception During Video File Read.") + finally: + cap.release() + + mm.soft_empty_cache() + gc.collect() + time.sleep(1) + + return (return_image_one, return_image_two, width, height, ) + class WarpedWanImageToVideo: @classmethod def INPUT_TYPES(s): @@ -3367,7 +3826,7 @@ class WarpedWanImageToVideo: RETURN_NAMES = ("positive", "negative", "latent", "num_frames", ) FUNCTION = "encode" - CATEGORY = "Warped/Video/Conditioning" + CATEGORY = "Warped/Wan/Conditioning" def encode(self, positive, negative, vae, width, height, length, batch_size, start_image=None, clip_vision_output=None): latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device()) @@ -3392,6 +3851,85 @@ class WarpedWanImageToVideo: out_latent["samples"] = latent return (positive, negative, out_latent, length, ) +class WarpedHunyuanImageToVideo: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "images": ("IMAGE", ), + "positive": ("CONDITIONING", ), + "vae": ("VAE", ), + "num_frames": ("INT", {"default": 53, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), + "guidance_type": (["v1 (concat)", "v2 (replace)"], ) + }, + } + + RETURN_TYPES = ("CONDITIONING", "LATENT") + RETURN_NAMES = ("positive", "latent") + FUNCTION = "encode" + + CATEGORY = "Warped/Hunyuan/Conditioning" + + def encode(self, images, positive, vae, num_frames, guidance_type): + images.to(dtype=torch.float32, device=get_offload_device()) + + if len(images.shape) < 4: + images = images.unsqueeze() + + height = images.shape[1] + width = images.shape[2] + + out_latent = {} + + if images.shape[0] == 1: + print("Single Image") + latent = torch.zeros([1, 16, ((num_frames - 1) // 4) + 1, height // 8, width // 8], dtype=torch.float32, device=get_offload_device()) + + images = comfy.utils.common_upscale(images[:num_frames, :, :, :3].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) + + concat_latent_image = vae.encode(images) + mask = torch.ones((1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=images.device, dtype=images.dtype) + mask[:, :, :((images.shape[0] - 1) // 4) + 1] = 0.0 + + if guidance_type == "v1 (concat)": + cond = {"concat_latent_image": concat_latent_image, "concat_mask": mask} + else: + cond = {'guiding_frame_index': 0} + latent[:, :, :concat_latent_image.shape[2]] = concat_latent_image + out_latent["noise_mask"] = mask + + positive = node_helpers.conditioning_set_values(positive, cond) + + out_latent["samples"] = latent + + return (positive, out_latent) + + print("Shape Before: {}".format(images.shape)) + + images = comfy.utils.common_upscale(images.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) + # images = torch.mul(torch.ones((num_frames, height, width, images.shape[-1]), device=images.device, dtype=images.dtype), 0.5) + + print("Shape After: {}".format(images.shape)) + + concat_latent_image = partial_encode_tiled(vae, images) + + if len(concat_latent_image.shape) < 5: + concat_latent_image = concat_latent_image.unsqueeze(0) + + mask = torch.ones((1, 1, concat_latent_image.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=images.device, dtype=images.dtype) + # mask[:, :, :((images.shape[0] - 1) // 4) + 1] = 0.0 + + out_latent["samples"] = concat_latent_image + + if guidance_type == "v1 (concat)": + cond = {"concat_latent_image": concat_latent_image, "concat_mask": mask} + else: + cond = {'guiding_frame_index': 0} + # out_latent["noise_mask"] = mask + + positive = node_helpers.conditioning_set_values(positive, cond) + + return (positive, out_latent) + class WarpedImageFromBatch: @classmethod def INPUT_TYPES(s): @@ -3402,12 +3940,12 @@ class WarpedImageFromBatch: RETURN_TYPES = ("IMAGE",) FUNCTION = "frombatch" - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" def frombatch(self, image, batch_index, length): - print("\n-------------------------------------------------------------------------------") - print(image) - print("-------------------------------------------------------------------------------\n") + # print("\n-------------------------------------------------------------------------------") + # print(image) + # print("-------------------------------------------------------------------------------\n") s_in = image @@ -3419,353 +3957,6 @@ class WarpedImageFromBatch: s = s_in[batch_index:batch_index + length].clone() return (s,) -def teacache_hunyuanvideo_forward( - self, - img: Tensor, - img_ids: Tensor, - txt: Tensor, - txt_ids: Tensor, - txt_mask: Tensor, - timesteps: Tensor, - y: Tensor, - guidance: Tensor = None, - guiding_frame_index=None, - control=None, - transformer_options={}, - ) -> Tensor: - patches_replace = transformer_options.get("patches_replace", {}) - rel_l1_thresh = transformer_options.get("rel_l1_thresh") - coefficients = transformer_options.get("coefficients") - max_skip_steps = transformer_options.get("max_skip_steps") - - initial_shape = list(img.shape) - # running on sequences img - img = self.img_in(img) - vec = self.time_in(timestep_embedding(timesteps, 256, time_factor=1.0).to(img.dtype)) - - if guiding_frame_index is not None: - token_replace_vec = self.time_in(timestep_embedding(guiding_frame_index, 256, time_factor=1.0)) - vec_ = self.vector_in(y[:, :self.params.vec_in_dim]) - vec = torch.cat([(vec_ + token_replace_vec).unsqueeze(1), (vec_ + vec).unsqueeze(1)], dim=1) - frame_tokens = (initial_shape[-1] // self.patch_size[-1]) * (initial_shape[-2] // self.patch_size[-2]) - modulation_dims = [(0, frame_tokens, 0), (frame_tokens, None, 1)] - modulation_dims_txt = [(0, None, 1)] - else: - vec = vec + self.vector_in(y[:, :self.params.vec_in_dim]) - modulation_dims = None - modulation_dims_txt = None - - if self.params.guidance_embed: - if guidance is not None: - vec = vec + self.guidance_in(timestep_embedding(guidance, 256).to(img.dtype)) - - if txt_mask is not None and not torch.is_floating_point(txt_mask): - txt_mask = (txt_mask - 1).to(img.dtype) * torch.finfo(img.dtype).max - - txt = self.txt_in(txt, timesteps, txt_mask) - - ids = torch.cat((img_ids, txt_ids), dim=1) - pe = self.pe_embedder(ids) - - img_len = img.shape[1] - if txt_mask is not None: - attn_mask_len = img_len + txt.shape[1] - attn_mask = torch.zeros((1, 1, attn_mask_len), dtype=img.dtype, device=img.device) - attn_mask[:, 0, img_len:] = txt_mask - else: - attn_mask = None - - blocks_replace = patches_replace.get("dit", {}) - - # enable teacache - img_mod1, _ = self.double_blocks[0].img_mod(vec) - modulated_inp = self.double_blocks[0].img_norm1(img) - modulated_inp = apply_mod(modulated_inp, (1 + img_mod1.scale), img_mod1.shift, modulation_dims) - - if not hasattr(self, 'accumulated_rel_l1_distance'): - should_calc = True - self.accumulated_rel_l1_distance = 0 - self.skip_steps = 0 - elif self.skip_steps == max_skip_steps: - should_calc = True - self.accumulated_rel_l1_distance = 0 - self.skip_steps = 0 - else: - try: - self.accumulated_rel_l1_distance += poly1d(coefficients, ((modulated_inp-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean())) - if self.accumulated_rel_l1_distance < rel_l1_thresh: - should_calc = False - self.skip_steps += 1 - else: - should_calc = True - self.accumulated_rel_l1_distance = 0 - self.skip_steps = 0 - except: - should_calc = True - self.accumulated_rel_l1_distance = 0 - self.skip_steps = 0 - - self.previous_modulated_input = modulated_inp - - if not should_calc: - img += self.previous_residual.to(img.device) - else: - ori_img = img.clone() - for i, block in enumerate(self.double_blocks): - if ("double_block", i) in blocks_replace: - def block_wrap(args): - out = {} - out["img"], out["txt"] = block(img=args["img"], txt=args["txt"], vec=args["vec"], pe=args["pe"], attn_mask=args["attention_mask"], modulation_dims_img=args["modulation_dims_img"], modulation_dims_txt=args["modulation_dims_txt"]) - return out - - out = blocks_replace[("double_block", i)]({"img": img, "txt": txt, "vec": vec, "pe": pe, "attention_mask": attn_mask, 'modulation_dims_img': modulation_dims, 'modulation_dims_txt': modulation_dims_txt}, {"original_block": block_wrap}) - txt = out["txt"] - img = out["img"] - else: - img, txt = block(img=img, txt=txt, vec=vec, pe=pe, attn_mask=attn_mask, modulation_dims_img=modulation_dims, modulation_dims_txt=modulation_dims_txt) - - if control is not None: # Controlnet - control_i = control.get("input") - if i < len(control_i): - add = control_i[i] - if add is not None: - img += add - - img = torch.cat((img, txt), 1) - - for i, block in enumerate(self.single_blocks): - if ("single_block", i) in blocks_replace: - def block_wrap(args): - out = {} - out["img"] = block(args["img"], vec=args["vec"], pe=args["pe"], attn_mask=args["attention_mask"], modulation_dims=args["modulation_dims"]) - return out - - out = blocks_replace[("single_block", i)]({"img": img, "vec": vec, "pe": pe, "attention_mask": attn_mask, 'modulation_dims': modulation_dims}, {"original_block": block_wrap}) - img = out["img"] - else: - img = block(img, vec=vec, pe=pe, attn_mask=attn_mask, modulation_dims=modulation_dims) - - if control is not None: # Controlnet - control_o = control.get("output") - if i < len(control_o): - add = control_o[i] - if add is not None: - img[:, : img_len] += add - - img = img[:, : img_len] - self.previous_residual = (img - ori_img).to(mm.unet_offload_device()) - - img = self.final_layer(img, vec, modulation_dims=modulation_dims) # (N, T, patch_size ** 2 * out_channels) - - shape = initial_shape[-3:] - for i in range(len(shape)): - shape[i] = shape[i] // self.patch_size[i] - img = img.reshape([img.shape[0]] + shape + [self.out_channels] + self.patch_size) - img = img.permute(0, 4, 1, 5, 2, 6, 3, 7) - img = img.reshape(initial_shape[0], self.out_channels, initial_shape[2], initial_shape[3], initial_shape[4]) - return img - -def teacache_wanmodel_forward(self, x, timestep, context, clip_fea=None, transformer_options={}, **kwargs): - bs, c, t, h, w = x.shape - x = comfy.ldm.common_dit.pad_to_patch_size(x, self.patch_size) - patch_size = self.patch_size - t_len = ((t + (patch_size[0] // 2)) // patch_size[0]) - h_len = ((h + (patch_size[1] // 2)) // patch_size[1]) - w_len = ((w + (patch_size[2] // 2)) // patch_size[2]) - img_ids = torch.zeros((t_len, h_len, w_len, 3), device=x.device, dtype=x.dtype) - img_ids[:, :, :, 0] = img_ids[:, :, :, 0] + torch.linspace(0, t_len - 1, steps=t_len, device=x.device, dtype=x.dtype).reshape(-1, 1, 1) - img_ids[:, :, :, 1] = img_ids[:, :, :, 1] + torch.linspace(0, h_len - 1, steps=h_len, device=x.device, dtype=x.dtype).reshape(1, -1, 1) - img_ids[:, :, :, 2] = img_ids[:, :, :, 2] + torch.linspace(0, w_len - 1, steps=w_len, device=x.device, dtype=x.dtype).reshape(1, 1, -1) - img_ids = repeat(img_ids, "t h w c -> b (t h w) c", b=bs) - - freqs = self.rope_embedder(img_ids).movedim(1, 2) - return self.forward_orig(x, timestep, context, clip_fea, freqs, transformer_options)[:, :, :t, :h, :w] - -def teacache_wanmodel_forward_orig( - self, - x, - t, - context, - clip_fea=None, - freqs=None, - transformer_options={}, - ): - rel_l1_thresh = transformer_options.get("rel_l1_thresh") - coefficients = transformer_options.get("coefficients") - max_skip_steps = transformer_options.get("max_skip_steps") - cond_or_uncond = transformer_options.get("cond_or_uncond") - - # embeddings - x = self.patch_embedding(x.float()).to(x.dtype) - grid_sizes = x.shape[2:] - x = x.flatten(2).transpose(1, 2) - - # time embeddings - e = self.time_embedding( - sinusoidal_embedding_1d(self.freq_dim, t).to(dtype=x[0].dtype)) - e0 = self.time_projection(e).unflatten(1, (6, self.dim)) - - # context - context = self.text_embedding(context) - - if clip_fea is not None and self.img_emb is not None: - context_clip = self.img_emb(clip_fea) # bs x 257 x dim - context = torch.concat([context_clip, context], dim=1) - - # arguments - kwargs = dict( - e=e0, - freqs=freqs, - context=context) - - # enable teacache - modulated_inp = e.to(mm.unet_offload_device()) - if not hasattr(self, 'teacache_state'): - self.teacache_state = { - 0: {'should_calc': True, 'accumulated_rel_l1_distance': 0, 'previous_modulated_input': None, 'previous_residual': None, 'skip_steps': 0}, - 1: {'should_calc': True, 'accumulated_rel_l1_distance': 0, 'previous_modulated_input': None, 'previous_residual': None, 'skip_steps': 0} - } - - def update_cache_state(cache, modulated_inp): - if cache['skip_steps'] == max_skip_steps: - cache['should_calc'] = True - cache['accumulated_rel_l1_distance'] = 0 - cache['skip_steps'] = 0 - elif cache['previous_modulated_input'] is not None: - try: - if not self.teacache_state[0]["previous_residual"] is None: - cache['accumulated_rel_l1_distance'] += poly1d(coefficients, ((modulated_inp-cache['previous_modulated_input']).abs().mean() / cache['previous_modulated_input'].abs().mean())) - else: - cache['accumulated_rel_l1_distance'] += poly1d(INITIAL_COEFFICIENTS, ((modulated_inp-cache['previous_modulated_input']).abs().mean() / cache['previous_modulated_input'].abs().mean())) - - if cache['accumulated_rel_l1_distance'] < rel_l1_thresh: - cache['should_calc'] = False - cache['skip_steps'] += 1 - else: - cache['should_calc'] = True - cache['accumulated_rel_l1_distance'] = 0 - cache['skip_steps'] = 0 - except: - cache['should_calc'] = True - cache['accumulated_rel_l1_distance'] = 0 - cache['skip_steps'] = 0 - cache['previous_modulated_input'] = modulated_inp - - b = int(len(x) / len(cond_or_uncond)) - - for i, k in enumerate(cond_or_uncond): - update_cache_state(self.teacache_state[k], modulated_inp[i*b:(i+1)*b]) - - should_calc = False - for k in cond_or_uncond: - should_calc = (should_calc or self.teacache_state[k]['should_calc']) - - if not should_calc: - for i, k in enumerate(cond_or_uncond): - x[i*b:(i+1)*b] += self.teacache_state[k]['previous_residual'].to(x.device) - else: - ori_x = x.clone() - for block in self.blocks: - x = block(x, **kwargs) - for i, k in enumerate(cond_or_uncond): - self.teacache_state[k]['previous_residual'] = (x - ori_x)[i*b:(i+1)*b].to(mm.unet_offload_device()) - - # head - x = self.head(x, e) - - # unpatchify - x = self.unpatchify(x, grid_sizes) - return x - -def clear_teacache_state(self): - self.teacache_state = { - 0: {'should_calc': True, 'accumulated_rel_l1_distance': 0, 'previous_modulated_input': None, 'previous_residual': None, 'skip_steps': 0}, - 1: {'should_calc': True, 'accumulated_rel_l1_distance': 0, 'previous_modulated_input': None, 'previous_residual': None, 'skip_steps': 0} - } - -class WarpedTeaCache: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL", {"tooltip": "The diffusion model the TeaCache will be applied to."}), - "model_type": (["wan2.1_t2v_1.3B", "wan2.1_t2v_14B", "wan2.1_i2v_480p_14B", "wan2.1_i2v_720p_14B", "hunyuan_video"], {"default": "wan2.1_i2v_720p_14B", "tooltip": "Supported diffusion model."}), - "rel_l1_thresh": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 10.0, "step": 0.01, "tooltip": "How strongly to cache the output of diffusion model. This value must be non-negative."}), - "max_skip_steps": ([1, 2, 3], {"default": 3, "tooltip": "Max continuous skip steps."}) - } - } - - RETURN_TYPES = ("MODEL",) - RETURN_NAMES = ("model",) - FUNCTION = "apply_teacache" - CATEGORY = "Warped/TeaCache" - TITLE = "Warped TeaCache" - - def apply_teacache(self, model, model_type: str, rel_l1_thresh: float, max_skip_steps: int): - if rel_l1_thresh == 0: - return (model,) - - new_model = model.clone() - if 'transformer_options' not in new_model.model_options: - new_model.model_options['transformer_options'] = {} - new_model.model_options["transformer_options"]["rel_l1_thresh"] = rel_l1_thresh - new_model.model_options["transformer_options"]["max_skip_steps"] = max_skip_steps - new_model.model_options["transformer_options"]["coefficients"] = SUPPORTED_MODELS_COEFFICIENTS[model_type] - diffusion_model = new_model.get_model_object("diffusion_model") - - if "hunyuan_video" in model_type: - is_cfg = False - context = patch.multiple( - diffusion_model, - forward_orig=teacache_hunyuanvideo_forward.__get__(diffusion_model, diffusion_model.__class__) - ) - elif "wan2.1" in model_type: - is_cfg = True - context = patch.multiple( - diffusion_model, - forward=teacache_wanmodel_forward.__get__(diffusion_model, diffusion_model.__class__), - forward_orig=teacache_wanmodel_forward_orig.__get__(diffusion_model, diffusion_model.__class__) - ) - else: - raise ValueError(f"Unknown type {model_type}") - - if hasattr(diffusion_model, 'teacache_state'): - delattr(diffusion_model, 'teacache_state') - - def unet_wrapper_function(model_function, kwargs): - input = kwargs["input"] - timestep = kwargs["timestep"] - c = kwargs["c"] - cond_or_uncond = kwargs["cond_or_uncond"] - # referenced from https://github.com/kijai/ComfyUI-KJNodes/blob/d126b62cebee81ea14ec06ea7cd7526999cb0554/nodes/model_optimization_nodes.py#L868 - sigmas = c["transformer_options"]["sample_sigmas"] - matched_step_index = (sigmas == timestep[0]).nonzero() - if len(matched_step_index) > 0: - current_step_index = matched_step_index.item() - else: - current_step_index = 0 - for i in range(len(sigmas) - 1): - # walk from beginning of steps until crossing the timestep - if (sigmas[i] - timestep) * (sigmas[i + 1] - timestep) <= 0: - current_step_index = i - break - - if current_step_index == 0: - if is_cfg: - # uncond first - if (1 in cond_or_uncond) and hasattr(diffusion_model, 'teacache_state'): - delattr(diffusion_model, 'teacache_state') - else: - if hasattr(diffusion_model, 'accumulated_rel_l1_distance'): - delattr(diffusion_model, 'accumulated_rel_l1_distance') - - with context: - return model_function(input, timestep, **c) - - new_model.set_model_unet_function_wrapper(unet_wrapper_function) - - return (new_model,) - class WarpedBaseWanLoraLoader: """Base class for Wan LoRA loading functionality""" @@ -3885,29 +4076,11 @@ class WarpedBaseWanLoraLoader: filtered_lora = self.filter_lora_keys(lora, block_settings) new_model, new_clip = load_lora_for_models(model, clip, filtered_lora, strength_model, strength_clip) - # print("\n=============================================================") - # print("Normal") - # print(new_model) - # print("=============================================================\n") - # - # print("\n=============================================================") - # print(new_clip) - # print("=============================================================\n") - return (new_model, new_clip,) except Exception as e: logger.error(f"Error applying LoRA {lora_name}: {str(e)}") - # print("\n=============================================================") - # print("Exception") - # print(model) - # print("=============================================================\n") - # - # print("\n=============================================================") - # print(clip) - # print("=============================================================\n") - return (model, clip, ) class WarpedWanLoadAndEditLoraBlocks(WarpedBaseWanLoraLoader): @@ -3934,7 +4107,7 @@ class WarpedWanLoadAndEditLoraBlocks(WarpedBaseWanLoraLoader): RETURN_TYPES = ("MODEL", "CLIP", ) RETURN_NAMES = ("model", "clip", ) FUNCTION = "load_lora" - CATEGORY = "Warped/WanTools" + CATEGORY = "Warped/Wan/Lora" @classmethod def IS_CHANGED(s, model, clip, lora_name: str, strength_model: float, strength_clip: float, save_settings: bool, **kwargs): @@ -3958,498 +4131,379 @@ class WarpedWanLoadAndEditLoraBlocks(WarpedBaseWanLoraLoader): return (return_model, return_clip,) -class WarpedHunyuanImageToVideo: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "images": ("IMAGE", ), - "positive": ("CONDITIONING", ), - "vae": ("VAE", ), - "num_frames": ("INT", {"default": 53, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}), - "guidance_type": (["v1 (concat)", "v2 (replace)"], ) - }, - } - - RETURN_TYPES = ("CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "latent") - FUNCTION = "encode" - - CATEGORY = "Warped/Video/Conditioning" - - def encode(self, images, positive, vae, num_frames, guidance_type): - images.to(dtype=torch.float32, device=get_offload_device()) - - if len(images.shape) < 4: - images = images.unsqueeze() - - height = images.shape[1] - width = images.shape[2] - - out_latent = {} - - if images.shape[0] == 1: - print("Single Image") - latent = torch.zeros([1, 16, ((num_frames - 1) // 4) + 1, height // 8, width // 8], dtype=torch.float32, device=get_offload_device()) - - images = comfy.utils.common_upscale(images[:num_frames, :, :, :3].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - - concat_latent_image = vae.encode(images) - mask = torch.ones((1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=images.device, dtype=images.dtype) - mask[:, :, :((images.shape[0] - 1) // 4) + 1] = 0.0 - - if guidance_type == "v1 (concat)": - cond = {"concat_latent_image": concat_latent_image, "concat_mask": mask} - else: - cond = {'guiding_frame_index': 0} - latent[:, :, :concat_latent_image.shape[2]] = concat_latent_image - out_latent["noise_mask"] = mask - - positive = node_helpers.conditioning_set_values(positive, cond) - - out_latent["samples"] = latent - - return (positive, out_latent) - - print("Shape Before: {}".format(images.shape)) - - images = comfy.utils.common_upscale(images.movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1) - # images = torch.mul(torch.ones((num_frames, height, width, images.shape[-1]), device=images.device, dtype=images.dtype), 0.5) - - print("Shape After: {}".format(images.shape)) - - concat_latent_image = partial_encode_tiled(vae, images) - - if len(concat_latent_image.shape) < 5: - concat_latent_image = concat_latent_image.unsqueeze(0) - - mask = torch.ones((1, 1, concat_latent_image.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=images.device, dtype=images.dtype) - # mask[:, :, :((images.shape[0] - 1) // 4) + 1] = 0.0 - - out_latent["samples"] = concat_latent_image - - if guidance_type == "v1 (concat)": - cond = {"concat_latent_image": concat_latent_image, "concat_mask": mask} - else: - cond = {'guiding_frame_index': 0} - # out_latent["noise_mask"] = mask - - positive = node_helpers.conditioning_set_values(positive, cond) - - return (positive, out_latent) - -class WarpedImageResizeKJStyle: - upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "width": ("INT", { "default": 512, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1, }), - "height": ("INT", { "default": 512, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1, }), - "upscale_method": (s.upscale_methods,), - "keep_proportion": ("BOOLEAN", { "default": False }), - "divisible_by": ("INT", { "default": 2, "min": 0, "max": 512, "step": 1, }), - }, - "optional" : { - "width_input": ("INT", { "forceInput": True}), - "height_input": ("INT", { "forceInput": True}), - "get_image_size": ("IMAGE",), - "crop": (["disabled","center"],), - } - } - - RETURN_TYPES = ("IMAGE", "INT", "INT",) - RETURN_NAMES = ("IMAGE", "width", "height",) - FUNCTION = "resize" - CATEGORY = "Warped/Image" - DESCRIPTION = """ -Resizes the image to the specified width and height. -Size can be retrieved from the inputs, and the final scale -is determined in this order of importance: -- get_image_size -- width_input and height_input -- width and height widgets - -Keep proportions keeps the aspect ratio of the image, by -highest dimension. -""" - - def resize(self, image, width, height, keep_proportion, upscale_method, divisible_by, - width_input=None, height_input=None, get_image_size=None, crop="disabled"): - B, H, W, C = image.shape - - if width_input: - width = width_input - if height_input: - height = height_input - if get_image_size is not None: - _, height, width, _ = get_image_size.shape - - if keep_proportion and get_image_size is None: - # If one of the dimensions is zero, calculate it to maintain the aspect ratio - if width == 0 and height != 0: - ratio = height / H - width = round(W * ratio) - elif height == 0 and width != 0: - ratio = width / W - height = round(H * ratio) - elif width != 0 and height != 0: - # Scale based on which dimension is smaller in proportion to the desired dimensions - ratio = min(width / W, height / H) - width = round(W * ratio) - height = round(H * ratio) - else: - if width == 0: - width = W - if height == 0: - height = H - - if divisible_by > 1 and get_image_size is None: - width = width - (width % divisible_by) - height = height - (height % divisible_by) - - image = image.movedim(-1,1) - image = utils.common_upscale(image, width, height, upscale_method, crop) - image = image.movedim(1,-1) - - return(image, image.shape[2], image.shape[1],) - -class WarpedImageResize: - upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] - @classmethod - def INPUT_TYPES(self): - return { - "required": { - "image": ("IMAGE",), - "width": ("INT", { "default": 400, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1, }), - "height": ("INT", { "default": 720, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1, }), - "upscale_method": (self.upscale_methods, {"default": "lanczos"}), - "crop": (["center", "top_left", "top_right", "bottom_left", "bottom_right", "top_center", "bottom_center"], {"default": "center"}), - }, - } - - RETURN_TYPES = ("IMAGE", "IMAGE", "INT", "INT",) - RETURN_NAMES = ("IMAGE", "scale_orig_image", "width", "height",) - FUNCTION = "resize" - CATEGORY = "Warped/Image" - DESCRIPTION = """ - Resizes the image to the specified width and height. - Size can be retrieved from the inputs, and the final scale - is determined in this order of importance: - - get_image_size - - width_input and height_input - - width and height widgets - - Keep proportions keeps the aspect ratio of the image, by - highest dimension. - """ - - def resize(self, image, width, height, upscale_method, crop="center"): - print("Original Image Shape: {}".format(image.shape)) - - B, H, W, C = image.shape - self.is_downsize = False - - scaled_image = image.clone().detach() - test_image1 = image.clone().detach() - test_image2 = image.clone().detach() - - is_long_side, orig_is_landscape, new_is_landscape, upscale_required = self.determine_side_to_scale(H, W, width, height) - - print("Automatic Value Determination: is_long_side: {} | original_is_landscape: {} | new_is_landscape: {} | upscale_required {}".format(is_long_side, orig_is_landscape, new_is_landscape, upscale_required)) - - if upscale_required: - W = int(W / 16) * 16 - H = int(H / 16) * 16 - - print("Modified Width: {} | Height: {}".format(W, H)) - - # Scale based on which dimension is smaller in proportion to the desired dimensions - ratio = max(width / W, height / H) - temp_width = round(W * ratio) - temp_height = round(H * ratio) - - print("Aspect Ratio Change Required: temp_width: {} | temp_height: {}".format(temp_width, temp_height)) - - image = image.movedim(-1,1) - image = self.upscale(image, temp_width, temp_height, upscale_method, crop) - image = image.movedim(1,-1) - - if orig_is_landscape: - if is_long_side: - image = self.scale_to_side(image, width, is_long_side) - else: - image = self.scale_to_side(image, height, is_long_side) - else: - if is_long_side: - image = self.scale_to_side(image, height, is_long_side) - else: - image = self.scale_to_side(image, width, is_long_side) - - scaled_image = image.clone().detach() - - image = self.crop(image, width, height, crop) - - return(image, scaled_image, image.shape[2], image.shape[1],) - - if (width < W) or (height < H): - # if same orientation - if (orig_is_landscape and new_is_landscape) or (not orig_is_landscape and not new_is_landscape): - if is_long_side: - if not new_is_landscape: - image = self.scale_to_side(image, height, is_long_side) - else: - image = self.scale_to_side(image, width, is_long_side) - else: - if new_is_landscape: - image = self.scale_to_side(image, height, is_long_side) - else: - image = self.scale_to_side(image, width, is_long_side) - # if original is landscape and new is portrait or original is portrait and new is landscape - else: - if is_long_side: - image = self.scale_to_side(image, width, is_long_side) - else: - image = self.scale_to_side(image, height, is_long_side) - - scaled_image = image.clone().detach() - - B, H, W, C = image.shape - self.is_downsize = True - - if self.is_downsize: - image = self.crop(image, width, height, crop) - return(image, scaled_image, image.shape[2], image.shape[1],) - - if (orig_is_landscape and new_is_landscape) or ((not orig_is_landscape) and (not new_is_landscape)): - image = image.movedim(-1,1) - image = self.upscale(image, width, height, upscale_method, crop) - image = image.movedim(1,-1) - - return(image, image, image.shape[2], image.shape[1],) - - if orig_is_landscape: - temp_ratio = round(height // H) - temp_width = round(W * temp_ratio) - - image = image.movedim(-1,1) - image = self.upscale(image, temp_width, height, upscale_method, "disabled") - image = image.movedim(1,-1) - - scaled_image = image.clone().detach() - image = self.crop(image, width, height, crop) - - return(image, scaled_image, image.shape[2], image.shape[1],) - - temp_ratio = round(width // W) - temp_height = round(H * temp_ratio) - - image = image.movedim(-1,1) - image = self.upscale(image, width, temp_height, upscale_method, "disabled") - image = image.movedim(1,-1) - - scaled_image = image.clone().detach() - image = self.crop(image, width, height, crop) - - return(image, scaled_image, image.shape[2], image.shape[1],) - - def determine_side_to_scale(self, original_height, original_width, width, height): - original_is_landscape = False - new_is_landscape = False - - if original_width > original_height: - original_is_landscape = True - - if width > height: - new_is_landscape = True - - if (original_width == original_height): - if width <= height: - original_is_landscape = True - - if width < height: - new_is_landscape = True - else: - new_is_landscape = original_is_landscape - else: - original_is_landscape = True - new_is_landscape = True - - is_long_side = True - upscale_required = False - - if (not new_is_landscape and original_is_landscape) or (new_is_landscape and not original_is_landscape): - is_long_side = False - else: - if original_is_landscape: - temp_ratio = round(width // original_width) - temp_height = round(temp_ratio * height) - - if temp_height < height: - is_long_side = False - upscale_required = True - else: - temp_ratio = round(height // original_height) - temp_width = round(temp_ratio * width) - - if temp_width < width: - is_long_side = False - upscale_required = True - - return is_long_side, original_is_landscape, new_is_landscape, upscale_required - - def crop(self, image, width, height, crop_type): - new_image = tensor2pilSwap(image) - new_image = new_image[0] - - print("Image width: {} height: {} | New width: {} height: {}".format(new_image.width, new_image.height, width, height)) - - #(left, upper, right, lower) - if crop_type == "top_left": - left = 0 - upper = 0 - right = width - lower = height - elif crop_type == "top_right": - left = new_image.width - width - upper = 0 - right = new_image.width - lower = height - elif crop_type == "top_center": - left = int(new_image.width // 2) - int(width // 2) - upper = 0 - right = (int(new_image.width // 2) - int(width // 2)) + width - lower = height - elif crop_type == "bottom_left": - left = 0 - upper = new_image.height - height - right = width - lower = new_image.height - elif crop_type == "bottom_right": - left = new_image.width - width - upper = new_image.height - height - right = new_image.width - lower = new_image.height - elif crop_type == "bottom_center": - left = int(new_image.width // 2) - int(width // 2) - upper = new_image.height - height - right = (int(new_image.width // 2) - int(width // 2)) + width - lower = new_image.height - elif crop_type == "center": - left = int(new_image.width // 2) - int(width // 2) - upper = int(new_image.height // 2) - int(height // 2) - right = (int(new_image.width // 2) - int(width // 2)) + width - lower = (int(new_image.height // 2) - int(height // 2)) + height - - print("Crop Locations: Left: {} | Upper: {} | Right: {} | Lower: {}".format(int(new_image.width // 2) - int(width // 2), 0, width, height)) - new_image = new_image.crop((left, upper, right, lower)) - - new_image = pil2tensorSwap(new_image) - - return new_image - - def upscale(self, samples, width, height, upscale_method, crop): - orig_shape = tuple(samples.shape) - if len(orig_shape) > 4: - samples = samples.reshape(samples.shape[0], samples.shape[1], -1, samples.shape[-2], samples.shape[-1]) - samples = samples.movedim(2, 1) - samples = samples.reshape(-1, orig_shape[1], orig_shape[-2], orig_shape[-1]) - - s = samples - if crop != "disabed": - old_width = samples.shape[-1] - old_height = samples.shape[-2] - old_aspect = old_width / old_height - new_aspect = width / height - x = 0 - y = 0 - - # ["disabled","center", "top_left", "top_right", "bottom_left", "bottom_right", "top_center", "bottom_center"] - # narrow(dim, start, length) - if crop == "center": - if old_aspect > new_aspect: - x = round((old_width - old_width * (new_aspect / old_aspect)) / 2) - elif old_aspect < new_aspect: - y = round((old_height - old_height * (old_aspect / new_aspect)) / 2) - - s = samples.narrow(-2, y, old_height - y * 2).narrow(-1, x, old_width - x * 2) - elif (s.shape[2] != height) or (s.shape[3] != width): - if self.is_downsize: - if len(s.shape) < 4: - s = s.unsqueeze(0) - - print("s.shape: {}".format(s.shape)) - - if crop == "top_left": - if width < height: - s = s.narrow(-1, 0, width) - else: - s = s.narrow(-2, 0, height) - elif crop == "top_right": - if width < height: - s = s.narrow(-1, s.shape[3] - width, width) - else: - s = s.narrow(-2, 0, height) - elif crop == "bottom_left": - if width < height: - s = s.narrow(-1, 0, width) - else: - s = s.narrow(-2, s.shape[2] - height, height) - elif crop == "bottom_right": - if width < height: - s = s.narrow(-1, s.shape[3] - width, width) - else: - s = s.narrow(-2, s.shape[2] - height, height) - elif crop == "top_center": - if width < height: - s = s.narrow(-1, round((s.shape[3] / 2) - (width / 2) - 1), width) - else: - s = s.narrow(-1, 0, height) - elif crop == "bottom_center": - if width < height: - s = s.narrow(-1, round((s.shape[3] / 2) - (width / 2) - 1), width) - print("s.shape 1: {}".format(s.shape)) - else: - s = s.narrow(-2, s.shape[2] - height, height) - print("s.shape 2: {}".format(s.shape)) - - if upscale_method == "bislerp": - out = utils.bislerp(s, width, height) - elif upscale_method == "lanczos": - out = utils.lanczos(s, width, height) - else: - out = torch.nn.functional.interpolate(s, size=(height, width), mode=upscale_method) - - if len(orig_shape) == 4: - return out - - out = out.reshape((orig_shape[0], -1, orig_shape[1]) + (height, width)) - return out.movedim(2, 1).reshape(orig_shape[:-2] + (height, width)) - - def scale_to_side(self, image, length=1024, scale_long_side=True): - img = tensor2pilSwap(image) - img = img[0] - - if scale_long_side: - if img.height >= img.width: - newHeight = length - newWidth = int(float(length / img.height) * img.width) - else: - newWidth = length - newHeight = int(float(length / img.width) * img.height) - else: - if img.height <= img.width: - newHeight = length - newWidth = int(float(length / img.height) * img.width) - else: - newWidth = length - newHeight = int(float(length / img.width) * img.height) - - tempImage = img.resize((newWidth, newHeight), resample=Image.BILINEAR) - - newImage = pil2tensorSwap([tempImage]) - - return newImage +# class WarpedImageResize: +# upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] +# @classmethod +# def INPUT_TYPES(self): +# return { +# "required": { +# "image": ("IMAGE",), +# "width": ("INT", { "default": 400, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1, }), +# "height": ("INT", { "default": 720, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 1, }), +# "upscale_method": (self.upscale_methods, {"default": "lanczos"}), +# "crop": (["center", "top_left", "top_right", "bottom_left", "bottom_right", "top_center", "bottom_center"], {"default": "center"}), +# "use_gpu": ("BOOLEAN", {"default": False}), +# }, +# } +# +# RETURN_TYPES = ("IMAGE", "IMAGE", "INT", "INT",) +# RETURN_NAMES = ("IMAGE", "scale_orig_image", "width", "height",) +# FUNCTION = "resize" +# CATEGORY = "Warped/General/Image" +# DESCRIPTION = """ +# Resizes the image to the specified width and height. +# Size can be retrieved from the inputs, and the final scale +# is determined in this order of importance: +# - get_image_size +# - width_input and height_input +# - width and height widgets +# +# Keep proportions keeps the aspect ratio of the image, by +# highest dimension. +# """ +# +# def resize(self, image, width, height, upscale_method, crop="center", use_gpu=False): +# print("Original Image Shape: {}".format(image.shape)) +# +# B, H, W, C = image.shape +# self.is_downsize = False +# +# if use_gpu: +# self.device = mm.get_torch_device() +# else: +# self.device = get_offload_device() +# +# scaled_image = image.clone().detach() +# test_image1 = image.clone().detach() +# test_image2 = image.clone().detach() +# +# is_long_side, orig_is_landscape, new_is_landscape, upscale_required = self.determine_side_to_scale(H, W, width, height) +# +# print("Automatic Value Determination: is_long_side: {} | original_is_landscape: {} | new_is_landscape: {} | upscale_required {}".format(is_long_side, orig_is_landscape, new_is_landscape, upscale_required)) +# +# if upscale_required: +# W = int(W / 16) * 16 +# H = int(H / 16) * 16 +# +# print("Modified Width: {} | Height: {}".format(W, H)) +# +# # Scale based on which dimension is smaller in proportion to the desired dimensions +# ratio = max(width / W, height / H) +# temp_width = round(W * ratio) +# temp_height = round(H * ratio) +# +# print("Aspect Ratio Change Required: temp_width: {} | temp_height: {}".format(temp_width, temp_height)) +# +# image = image.movedim(-1,1) +# image = self.upscale(image, temp_width, temp_height, upscale_method, crop) +# image = image.movedim(1,-1) +# +# if orig_is_landscape: +# if is_long_side: +# image = self.scale_to_side(image, width, is_long_side) +# else: +# image = self.scale_to_side(image, height, is_long_side) +# else: +# if is_long_side: +# image = self.scale_to_side(image, height, is_long_side) +# else: +# image = self.scale_to_side(image, width, is_long_side) +# +# scaled_image = image.clone().detach() +# +# image = self.crop(image, width, height, crop) +# +# return(image, scaled_image, image.shape[2], image.shape[1],) +# +# if (width < W) or (height < H): +# # if same orientation +# if (orig_is_landscape and new_is_landscape) or (not orig_is_landscape and not new_is_landscape): +# if is_long_side: +# if not new_is_landscape: +# image = self.scale_to_side(image, height, is_long_side) +# else: +# image = self.scale_to_side(image, width, is_long_side) +# else: +# if new_is_landscape: +# image = self.scale_to_side(image, height, is_long_side) +# else: +# image = self.scale_to_side(image, width, is_long_side) +# # if original is landscape and new is portrait or original is portrait and new is landscape +# else: +# if is_long_side: +# image = self.scale_to_side(image, width, is_long_side) +# else: +# image = self.scale_to_side(image, height, is_long_side) +# +# scaled_image = image.clone().detach() +# +# B, H, W, C = image.shape +# self.is_downsize = True +# +# if self.is_downsize: +# image = self.crop(image, width, height, crop) +# return(image, scaled_image, image.shape[2], image.shape[1],) +# +# if (orig_is_landscape and new_is_landscape) or ((not orig_is_landscape) and (not new_is_landscape)): +# image = image.movedim(-1,1) +# image = self.upscale(image, width, height, upscale_method, crop) +# image = image.movedim(1,-1) +# +# return(image, image, image.shape[2], image.shape[1],) +# +# if orig_is_landscape: +# temp_ratio = round(height // H) +# temp_width = round(W * temp_ratio) +# +# image = image.movedim(-1,1) +# image = self.upscale(image, temp_width, height, upscale_method, "disabled") +# image = image.movedim(1,-1) +# +# scaled_image = image.clone().detach() +# image = self.crop(image, width, height, crop) +# +# return(image, scaled_image, image.shape[2], image.shape[1],) +# +# temp_ratio = round(width // W) +# temp_height = round(H * temp_ratio) +# +# image = image.movedim(-1,1) +# image = self.upscale(image, width, temp_height, upscale_method, "disabled") +# image = image.movedim(1,-1) +# +# scaled_image = image.clone().detach() +# image = self.crop(image, width, height, crop) +# +# return(image, scaled_image, image.shape[2], image.shape[1],) +# +# def determine_side_to_scale(self, original_height, original_width, width, height): +# original_is_landscape = False +# new_is_landscape = False +# +# if original_width > original_height: +# original_is_landscape = True +# +# if width > height: +# new_is_landscape = True +# +# if (original_width == original_height): +# if width <= height: +# original_is_landscape = True +# +# if width < height: +# new_is_landscape = True +# else: +# new_is_landscape = original_is_landscape +# else: +# original_is_landscape = True +# new_is_landscape = True +# +# is_long_side = True +# upscale_required = False +# +# if (not new_is_landscape and original_is_landscape) or (new_is_landscape and not original_is_landscape): +# is_long_side = False +# else: +# if original_is_landscape: +# temp_ratio = round(width // original_width) +# temp_height = round(temp_ratio * height) +# +# if temp_height < height: +# is_long_side = False +# upscale_required = True +# else: +# temp_ratio = round(height // original_height) +# temp_width = round(temp_ratio * width) +# +# if temp_width < width: +# is_long_side = False +# upscale_required = True +# +# return is_long_side, original_is_landscape, new_is_landscape, upscale_required +# +# def crop(self, input_image, width, height, crop_type): +# image_batches_tuple = torch.split(input_image, 1, dim=0) +# image_batches_split = [item for item in image_batches_tuple] +# +# result_images = None +# for image in image_batches_split: +# new_image = tensor2pilSwap(image) +# new_image = new_image[0] +# +# print("Image width: {} height: {} | New width: {} height: {}".format(new_image.width, new_image.height, width, height)) +# +# #(left, upper, right, lower) +# if crop_type == "top_left": +# left = 0 +# upper = 0 +# right = width +# lower = height +# elif crop_type == "top_right": +# left = new_image.width - width +# upper = 0 +# right = new_image.width +# lower = height +# elif crop_type == "top_center": +# left = int(new_image.width // 2) - int(width // 2) +# upper = 0 +# right = (int(new_image.width // 2) - int(width // 2)) + width +# lower = height +# elif crop_type == "bottom_left": +# left = 0 +# upper = new_image.height - height +# right = width +# lower = new_image.height +# elif crop_type == "bottom_right": +# left = new_image.width - width +# upper = new_image.height - height +# right = new_image.width +# lower = new_image.height +# elif crop_type == "bottom_center": +# left = int(new_image.width // 2) - int(width // 2) +# upper = new_image.height - height +# right = (int(new_image.width // 2) - int(width // 2)) + width +# lower = new_image.height +# elif crop_type == "center": +# left = int(new_image.width // 2) - int(width // 2) +# upper = int(new_image.height // 2) - int(height // 2) +# right = (int(new_image.width // 2) - int(width // 2)) + width +# lower = (int(new_image.height // 2) - int(height // 2)) + height +# +# print("Crop Locations: Left: {} | Upper: {} | Right: {} | Lower: {}".format(int(new_image.width // 2) - int(width // 2), 0, width, height)) +# new_image = new_image.crop((left, upper, right, lower)) +# +# newImage = pil2tensorSwap([newImage], device=self.device) +# +# if not result_images is None: +# result_images = torch.cat((result_images, new_image), 0) +# else: +# result_images = new_image +# +# new_image = None +# +# mm.soft_empty_cache() +# gc.collect() +# time.sleep(1) +# +# return result_images +# +# def upscale(self, samples, width, height, upscale_method, crop): +# orig_shape = tuple(samples.shape) +# if len(orig_shape) > 4: +# samples = samples.reshape(samples.shape[0], samples.shape[1], -1, samples.shape[-2], samples.shape[-1]) +# samples = samples.movedim(2, 1) +# samples = samples.reshape(-1, orig_shape[1], orig_shape[-2], orig_shape[-1]) +# +# s = samples +# if crop != "disabed": +# old_width = samples.shape[-1] +# old_height = samples.shape[-2] +# old_aspect = old_width / old_height +# new_aspect = width / height +# x = 0 +# y = 0 +# +# if crop == "center": +# if old_aspect > new_aspect: +# x = round((old_width - old_width * (new_aspect / old_aspect)) / 2) +# elif old_aspect < new_aspect: +# y = round((old_height - old_height * (old_aspect / new_aspect)) / 2) +# +# s = samples.narrow(-2, y, old_height - y * 2).narrow(-1, x, old_width - x * 2) +# elif (s.shape[2] != height) or (s.shape[3] != width): +# if self.is_downsize: +# if len(s.shape) < 4: +# s = s.unsqueeze(0) +# +# print("s.shape: {}".format(s.shape)) +# +# if crop == "top_left": +# if width < height: +# s = s.narrow(-1, 0, width) +# else: +# s = s.narrow(-2, 0, height) +# elif crop == "top_right": +# if width < height: +# s = s.narrow(-1, s.shape[3] - width, width) +# else: +# s = s.narrow(-2, 0, height) +# elif crop == "bottom_left": +# if width < height: +# s = s.narrow(-1, 0, width) +# else: +# s = s.narrow(-2, s.shape[2] - height, height) +# elif crop == "bottom_right": +# if width < height: +# s = s.narrow(-1, s.shape[3] - width, width) +# else: +# s = s.narrow(-2, s.shape[2] - height, height) +# elif crop == "top_center": +# if width < height: +# s = s.narrow(-1, round((s.shape[3] / 2) - (width / 2) - 1), width) +# else: +# s = s.narrow(-1, 0, height) +# elif crop == "bottom_center": +# if width < height: +# s = s.narrow(-1, round((s.shape[3] / 2) - (width / 2) - 1), width) +# print("s.shape 1: {}".format(s.shape)) +# else: +# s = s.narrow(-2, s.shape[2] - height, height) +# print("s.shape 2: {}".format(s.shape)) +# +# if upscale_method == "bislerp": +# out = utils.bislerp(s, width, height) +# elif upscale_method == "lanczos": +# out = utils.lanczos(s, width, height) +# else: +# out = torch.nn.functional.interpolate(s, size=(height, width), mode=upscale_method) +# +# if len(orig_shape) == 4: +# return out +# +# out = out.reshape((orig_shape[0], -1, orig_shape[1]) + (height, width)) +# return out.movedim(2, 1).reshape(orig_shape[:-2] + (height, width)) +# +# def scale_to_side(self, input_image, length=1024, scale_long_side=True): +# image_batches_tuple = torch.split(input_image, 1, dim=0) +# image_batches_split = [item for item in image_batches_tuple] +# +# result_images = None +# +# for image in image_batches_split: +# img = tensor2pilSwap(image) +# img = img[0] +# +# if scale_long_side: +# if img.height >= img.width: +# newHeight = length +# newWidth = int(float(length / img.height) * img.width) +# else: +# newWidth = length +# newHeight = int(float(length / img.width) * img.height) +# else: +# if img.height <= img.width: +# newHeight = length +# newWidth = int(float(length / img.height) * img.width) +# else: +# newWidth = length +# newHeight = int(float(length / img.width) * img.height) +# +# tempImage = img.resize((newWidth, newHeight), resample=Image.BILINEAR) +# +# newImage = pil2tensorSwap([tempImage], device=self.device) +# +# if not result_images is None: +# result_images = torch.cat((result_images, new_image), 0) +# else: +# result_images = new_image +# +# new_image = None +# +# mm.soft_empty_cache() +# gc.collect() +# time.sleep(1) +# +# return result_images class WarpedImageScaleToSide: @classmethod @@ -4462,7 +4516,7 @@ class WarpedImageScaleToSide: }, } - CATEGORY = "Warped/Image" + CATEGORY = "Warped/General/Image" RETURN_TYPES = ("IMAGE", "INT", "INT", ) RETURN_NAMES = ("image", "width", "height") @@ -4527,172 +4581,9 @@ class WarpedImageScaleToSide: return (final_image, final_image.shape[1], final_image.shape[2],) -class WarpedHunyuanLoraCheck: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "lora": (['None'] + get_lora_list(),), - }, - } - - RETURN_TYPES = () - OUTPUT_NODE = True - OUTPUT_IS_LIST = (True,) - FUNCTION = "check_lora" - CATEGORY = "Warped/HunyuanTools" - DESCRIPTION = "" - - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: - """Load and filter a single LoRA model.""" - if not lora_name or strength == 0: - return {}, {} - - # Get the full path to the LoRA file - lora_path = folder_paths.get_full_path("loras", lora_name) - if not os.path.exists(lora_path): - raise ValueError(f"LoRA file not found: {lora_path}") - - # Load the LoRA weights - lora_weights = utils.load_torch_file(lora_path) - - return lora_weights - - def check_lora(self, lora): - if lora != "None": - # Load and filter the LoRA weights - lora_weights = self.load_lora(lora_1, 1.0, blocks_type_1) - else: - lora_weights = {} - - for key in lora_weights: - print(key) - - return {"ui": {"tags": [save_message]}} - def get_base_lora_dirs(): return folder_paths.get_folder_paths("loras") -# class WarpedLoadLorasBatch: -# def __init__(self): -# self.index = 0 -# self.base_lora_dir = "" -# self.sub_folder = "" -# -# @classmethod -# def INPUT_TYPES(cls): -# return { -# "required": { -# "base_lora_dir": (get_base_lora_dirs(), ), -# "lora_subdirectory": ("STRING", {"default": '', "multiline": False}), -# }, -# } -# -# RETURN_TYPES = ("STRING", "STRING", ) -# RETURN_NAMES = ("lora_name", "full_lora_path", ) -# FUNCTION = "load_batch_loras" -# -# CATEGORY = "Warped/LORA" -# -# def load_batch_loras(self, base_lora_dir, lora_subdirectory): -# self.sub_folder = lora_subdirectory -# self.base_lora_dir = base_lora_dir -# path = os.path.join(self.base_lora_dir, self.sub_folder) -# print(path) -# -# if not os.path.exists(path): -# return ("", "", ) -# -# index=0 -# mode="incremental_lora" -# label='Batch 001' -# suffix="" -# -# retry = False -# -# try: -# filename, full_filename = self.do_the_load(path, index, mode, label, suffix) -# print("WarpedLoadLorasBatch: Filename: {} | Full File Path: {}".format(filename, full_filename)) -# return (filename, full_filename, ) -# except: -# self.index = 0 -# retry = True -# -# if retry: -# filename, full_filename = self.do_the_load(path, index, mode, label, suffix) -# print("WarpedLoadLorasBatch: Retrying: Filename: {} | Full File Path: {}".format(filename, full_filename)) -# return (filename, full_filename, ) -# -# return ("", "", ) -# -# -# def do_the_load(self, path, index, mode, label, suffix): -# fl = self.BatchLoraLoader(path, label, '*', index) -# new_paths = fl.lora_paths -# -# filename = fl.lora_paths[self.index] -# -# filename = os.path.join(self.sub_folder, filename) -# full_filename = os.path.join(self.base_lora_dir, filename) -# -# self.index += 1 -# -# if self.index >= len(fl.lora_paths): -# self.index = 0 -# -# return filename, full_filename -# -# -# class BatchLoraLoader: -# def __init__(self, directory_path, label, pattern, index): -# self.lora_paths = [] -# self.load_loras(directory_path, pattern) -# self.lora_paths.sort() -# -# self.index = index -# self.label = label -# -# def load_loras(self, directory_path, pattern): -# for file_name in glob.glob(os.path.join(directory_path, pattern), recursive=True): -# temp_strings = file_name.split('\\') -# file_name = temp_strings[len(temp_strings) - 1] -# -# if file_name.lower().endswith("safetensors"): -# # abs_file_path = os.path.abspath(file_name) -# self.lora_paths.append(file_name) -# -# def get_lora_by_id(self, lora_id): -# if lora_id < 0 or lora_id >= len(self.lora_paths): -# cstr(f"Invalid lora index `{lora_id}`").error.print() -# return -# -# return self.lora_paths[lora_id] -# -# def get_next_lora(self): -# if self.index >= len(self.lora_paths): -# self.index = 0 -# -# lora_path = self.lora_paths[self.index] -# self.index += 1 -# -# if self.index == len(self.lora_paths): -# self.index = 0 -# -# cstr(f'{cstr.color.YELLOW}{self.label}{cstr.color.END} Index: {self.index}').msg.print() -# -# return lora_path -# -# def get_current_lora(self): -# if self.index >= len(self.lora_paths): -# self.index = 0 -# lora_path = self.lora_paths[self.index] -# -# return lora_path -# -# @classmethod -# def IS_CHANGED(cls, **kwargs): -# return float("NaN") - def get_lora_directories(): lora_dirs = get_base_lora_dirs() @@ -4739,7 +4630,7 @@ class WarpedLoadLorasBatchByPrefix: RETURN_NAMES = ("lora_name", "full_lora_path", ) FUNCTION = "load_batch_loras" - CATEGORY = "Warped/LORA" + CATEGORY = "Warped/General/Lora" def load_batch_loras(self, lora_dir, lora_prefix): self.lora_dir = lora_dir @@ -4871,86 +4762,10 @@ class WarpedHunyuanVideoLoraLoader: RETURN_TYPES = ("MODEL",) RETURN_NAMES = ("model",) FUNCTION = "load_lora" - CATEGORY = "Warped/LORA/Hunyuan" + CATEGORY = "Warped/Hunyuan/Lora" OUTPUT_NODE = False DESCRIPTION = "LoRA, single blocks double blocks" - def convert_key_format(self, key: str) -> str: - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - - return key - - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - if blocks_type == "all": - return lora - - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - - if blocks_type == "single_blocks" in base_key: - filtered_lora[key] = value - elif blocks_type == "double_blocks" in base_key: - filtered_lora[key] = value - - return filtered_lora - - def check_for_musubi(self, lora: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: - """Checks for and converts from Musubi Tuner format which supports Network Alpha and uses different naming. Largely copied from that project""" - prefix = "lora_unet_" - musubi = False - lora_alphas = {} - for key, value in lora.items(): - if key.startswith(prefix): - lora_name = key.split(".", 1)[0] # before first dot - if lora_name not in lora_alphas and "alpha" in key: - lora_alphas[lora_name] = value - musubi = True - if musubi: - print("Loading Musubi Tuner format LoRA...") - converted_lora = {} - for key, weight in lora.items(): - if key.startswith(prefix): - if "alpha" in key: - continue - lora_name = key.split(".", 1)[0] # before first dot - # HunyuanVideo lora name to module name: ugly but works - module_name = lora_name[len(prefix) :] # remove "lora_unet_" - module_name = module_name.replace("_", ".") # replace "_" with "." - module_name = module_name.replace("double.blocks.", "double_blocks.") # fix double blocks - module_name = module_name.replace("single.blocks.", "single_blocks.") # fix single blocks - module_name = module_name.replace("img.", "img_") # fix img - module_name = module_name.replace("txt.", "txt_") # fix txt - module_name = module_name.replace("attn.", "attn_") # fix attn - diffusers_prefix = "diffusion_model" - if "lora_down" in key: - new_key = f"{diffusers_prefix}.{module_name}.lora_A.weight" - dim = weight.shape[0] - elif "lora_up" in key: - new_key = f"{diffusers_prefix}.{module_name}.lora_B.weight" - dim = weight.shape[1] - else: - print(f"unexpected key: {key} in Musubi LoRA format") - continue - # scale weight by alpha - if lora_name in lora_alphas: - # we scale both down and up, so scale is sqrt - scale = lora_alphas[lora_name] / dim - scale = scale.sqrt() - weight = weight * scale - else: - print(f"missing alpha for {lora_name}") - - converted_lora[new_key] = weight - return converted_lora - else: - print("Loading Diffusers format LoRA...") - return lora - def load_lora(self, model, lora_name: str, strength: float, blocks_type: str): """ Parameters @@ -4987,8 +4802,8 @@ class WarpedHunyuanVideoLoraLoader: lora = load_torch_file(lora_path) self.loaded_lora = (lora_path, lora) - diffusers_lora = self.check_for_musubi(lora) - filtered_lora = self.filter_lora_keys(diffusers_lora, blocks_type) + diffusers_lora = convert_lora(lora, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(diffusers_lora, blocks_type) new_model, _ = load_lora_for_models(model, None, filtered_lora, strength, 0) if new_model is not None: @@ -5027,7 +4842,7 @@ class WarpedFramepackMultiLoraSelect: RETURN_NAMES = ("lora", ) FUNCTION = "select_multiple_loras" CATEGORY = "Warped/Framepack/Lora" - DESCRIPTION = "Select a Hunyuan LoRA model" + DESCRIPTION = "Select a Hunyuan LoRA models" def select_multiple_loras(self, **kwargs): loras_list = [] @@ -5042,14 +4857,73 @@ class WarpedFramepackMultiLoraSelect: temp_strength = kwargs.get(f"strength_0{i}") temp_fuse_lora = kwargs.get(f"fuse_lora_0{i}") - # print("lora_name: {}".format(temp_lora_name)) - # print("strength: {}".format(temp_strength)) - # print("fuse_lora: {}".format(temp_fuse_lora)) + if (temp_lora_name != "None") and (Decimal(temp_strength).compare(Decimal(0.0)) != 0): + lora = { + "path": folder_paths.get_full_path("loras", temp_lora_name), + "strength": temp_strength, + "name": temp_lora_name.split(".")[0], + "fuse_lora": temp_fuse_lora, + } + + loras_list.append(lora) + + if len(loras_list) > 0: + return (loras_list,) + else: + return (None,) + +class WarpedFramepackMultiLoraSelectExt: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "lora_01": (['None'] + folder_paths.get_filename_list("loras"), {"tooltip": "LORA models are expected to have .safetensors extension"}), + "strength_01": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.0001, "tooltip": "LORA strength, set to 0.0 to unmerge the LORA"}), + "blocks_01": (["all", "double_blocks", "single_blocks"], {"default": "all", "tooltip": "all, single only, or double only block."}), + "fuse_lora_01": ("BOOLEAN", {"default": False, "tooltip": "Fuse the LORA model with the base model. This is recommended for better performance."}), + "lora_02": (['None'] + folder_paths.get_filename_list("loras"), {"tooltip": "LORA models are expected to have .safetensors extension"}), + "strength_02": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.0001, "tooltip": "LORA strength, set to 0.0 to unmerge the LORA"}), + "blocks_02": (["all", "double_blocks", "single_blocks"], {"default": "all", "tooltip": "all, single only, or double only block."}), + "fuse_lora_02": ("BOOLEAN", {"default": False, "tooltip": "Fuse the LORA model with the base model. This is recommended for better performance."}), + "lora_03": (['None'] + folder_paths.get_filename_list("loras"), {"tooltip": "LORA models are expected to have .safetensors extension"}), + "strength_03": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.0001, "tooltip": "LORA strength, set to 0.0 to unmerge the LORA"}), + "blocks_03": (["all", "double_blocks", "single_blocks"], {"default": "all", "tooltip": "all, single only, or double only block."}), + "fuse_lora_03": ("BOOLEAN", {"default": False, "tooltip": "Fuse the LORA model with the base model. This is recommended for better performance."}), + "lora_04": (['None'] + folder_paths.get_filename_list("loras"), {"tooltip": "LORA models are expected to have .safetensors extension"}), + "strength_04": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.0001, "tooltip": "LORA strength, set to 0.0 to unmerge the LORA"}), + "blocks_04": (["all", "double_blocks", "single_blocks"], {"default": "all", "tooltip": "all, single only, or double only block."}), + "fuse_lora_04": ("BOOLEAN", {"default": False, "tooltip": "Fuse the LORA model with the base model. This is recommended for better performance."}), + }, + "optional": { + "prev_lora":("FPLORA", {"default": None, "tooltip": "For loading multiple LoRAs"}), + } + } + + RETURN_TYPES = ("FPLORA",) + RETURN_NAMES = ("lora", ) + FUNCTION = "select_multiple_loras" + CATEGORY = "Warped/Framepack/Lora" + DESCRIPTION = "Select a Hunyuan LoRA models" + + def select_multiple_loras(self, **kwargs): + loras_list = [] + + prev_lora = kwargs.get(f"prev_lora") + + if prev_lora is not None: + loras_list.extend(prev_lora) + + for i in range(1, 5): + temp_lora_name = kwargs.get(f"lora_0{i}") + temp_strength = kwargs.get(f"strength_0{i}") + temp_blocks = kwargs.get(f"blocks_0{i}") + temp_fuse_lora = kwargs.get(f"fuse_lora_0{i}") if (temp_lora_name != "None") and (Decimal(temp_strength).compare(Decimal(0.0)) != 0): lora = { "path": folder_paths.get_full_path("loras", temp_lora_name), "strength": temp_strength, + "blocks": temp_blocks, "name": temp_lora_name.split(".")[0], "fuse_lora": temp_fuse_lora, } @@ -5097,7 +4971,7 @@ class WarpedHunyuanMultiLoraLoader: RETURN_NAMES = ("model", "lora_metadata") OUTPUT_IS_LIST = (False, True) FUNCTION = "load_multiple_loras" - CATEGORY = "Warped/LORA/Hunyuan" + CATEGORY = "Warped/Hunyuan/Lora" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." def convert_key_format(self, key: str) -> str: @@ -5109,17 +4983,6 @@ class WarpedHunyuanMultiLoraLoader: break return key - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - """Filter LoRA weights based on block type.""" - if blocks_type == "all": - return lora - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - if blocks_type in base_key: - filtered_lora[key] = value - return filtered_lora - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -5133,12 +4996,13 @@ class WarpedHunyuanMultiLoraLoader: # Load the LoRA weights lora_weights = utils.load_torch_file(lora_path) + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") + # Filter the LoRA weights based on the block type - filtered_lora = self.filter_lora_keys(lora_weights, blocks_type) + filtered_lora = filter_lora_keys(lora_weights, blocks_type) return lora_weights, filtered_lora -# def load_multiple_loras(self, model, lora_name=None, strength=1.00, blocks_type="all", **kwargs): def load_multiple_loras(self, model, **kwargs): """Load and apply multiple LoRA models.""" from comfy.sd import load_lora_for_models @@ -5153,7 +5017,7 @@ class WarpedHunyuanMultiLoraLoader: print("Lora Name: {} | Strength: {} | Block Types: {}".format(temp_lora_name, temp_strength, temp_blocks_type)) lora_metadata.append("{}".format("Lora: {} | Strength: {} | Block Types: {}".format(temp_lora_name, temp_strength, temp_blocks_type))) - lora_weights, filtered_lora = self.load_lora(temp_lora_name, temp_strength, temp_blocks_type) + lora_weights, filtered_lora = self.load_lora(temp_lora_name, 1.0, "all") # Apply the LoRA weights to the model if filtered_lora: @@ -5167,7 +5031,8 @@ class WarpedHunyuanMultiLoraLoader: if temp_lora_name != "None" and temp_strength != 0: lora_metadata.append("{}".format("Lora: {} | Strength: {} | Block Types: {}".format(temp_lora_name, temp_strength, temp_blocks_type))) # Load and filter the LoRA weights - lora_weights, filtered_lora = self.load_lora(temp_lora_name, temp_strength, temp_blocks_type) + lora_weights, filtered_lora = self.load_lora(temp_lora_name, 1.0, "all") + # Apply the LoRA weights to the model if filtered_lora: @@ -5183,6 +5048,113 @@ class WarpedHunyuanMultiLoraLoader: f"{kwargs.get('lora_03')}_{kwargs.get('strength_03')}_{kwargs.get('blocks_type_03')}_" \ f"{kwargs.get('lora_04')}_{kwargs.get('strength_04')}_{kwargs.get('blocks_type_04')}" +class WarpedMultiLoraLoader: + def __init__(self): + self.loaded_lora = None + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}), + "clip": ("CLIP", {"tooltip": "The CLIP model the LoRA will be applied to."}), + "lora_01": (['None'] + folder_paths.get_filename_list("loras"),), + "strength_model_01": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "strength_clip_01": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}), + "lora_02": (['None'] + folder_paths.get_filename_list("loras"),), + "strength_model_02": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "strength_clip_02": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}), + "lora_03": (['None'] + folder_paths.get_filename_list("loras"),), + "strength_model_03": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "strength_clip_03": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}), + "lora_04": (['None'] + folder_paths.get_filename_list("loras"),), + "strength_model_04": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "strength_clip_04": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}), + }, + "optional": { + "lora_name": ("STRING", {"default": None, "forceInput": True}), + "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "strength_clip": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}), + }, + } + + RETURN_TYPES = ("MODEL", "CLIP", "STRING", ) + RETURN_NAMES = ("model", "clip", "lora_metadata",) + OUTPUT_IS_LIST = (False, False, True) + FUNCTION = "load_multiple_loras" + CATEGORY = "Warped/General/Loaders" + DESCRIPTION = "Load and apply multiple LoRA models with different strengths. Model input is required." + + def load_lora(self, model, clip, lora_name, strength_model, strength_clip): + if strength_model == 0 and strength_clip == 0: + return model, clip + + lora_path = folder_paths.get_full_path("loras", lora_name) + lora_path = os.path.abspath(lora_path) + + if not os.path.exists(lora_path): + raise ValueError(f"LoRA file not found: {lora_path}") + + lora = None + if self.loaded_lora is not None: + if self.loaded_lora[0] == lora_path: + lora = self.loaded_lora[1] + else: + self.loaded_lora = None + + if lora is None: + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + self.loaded_lora = (lora_path, lora) + + model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip) + + return model_lora, clip_lora + + def load_multiple_loras(self, model, clip, **kwargs): + """Load and apply multiple LoRA models.""" + from comfy.sd import load_lora_for_models + + temp_lora_name = kwargs.get(f"lora_name") + temp_strength_model = kwargs.get(f"strength_model") + temp_strength_clip = kwargs.get(f"strength_clip") + + temp_model = model + temp_clip = clip + + lora_metadata = [] + + if not temp_lora_name is None and ((temp_strength_model != 0) or (temp_strength_clip != 0)): + print("Lora Name: {} | Strength Model: {} | Strength Clip: {}".format(temp_lora_name, temp_strength_model, temp_strength_clip)) + lora_metadata.append("Lora: {} | Strength Model: {} | Strength clip: {}".format(temp_lora_name, temp_strength_model, temp_strength_clip)) + + temp_model, temp_clip = self.load_lora(temp_model, temp_clip, temp_lora_name, temp_strength_model, temp_strength_clip) + + for i in range(1, 5): + temp_lora_name = kwargs.get(f"lora_0{i}") + + if temp_lora_name == "None": + continue + + temp_strength_model = kwargs.get(f"strength_model_0{i}") + temp_strength_clip = kwargs.get(f"strength_clip_0{i}") + + print("Lora Name: {} | Strength Model: {} | Strength Clip: {}".format(temp_lora_name, temp_strength_model, temp_strength_clip)) + + if not temp_lora_name is None and ((temp_strength_model != 0) or (temp_strength_clip != 0)): + lora_metadata.append("Lora: {} | Strength Model: {} | Strength clip: {}".format(temp_lora_name, temp_strength_model, temp_strength_clip)) + # Load LoRA weights + temp_model, temp_clip = self.load_lora(temp_model, temp_clip, temp_lora_name, temp_strength_model, temp_strength_clip) + + return (temp_model, temp_clip, lora_metadata, ) + + @classmethod + def IS_CHANGED(s, **kwargs): + return f"{kwargs.get('lora_name')}_{kwargs.get('strength')}_{kwargs.get('blocks_type')}_" \ + f"{kwargs.get('lora_01')}_{kwargs.get('strength_01')}_{kwargs.get('blocks_type_01')}_" \ + f"{kwargs.get('lora_02')}_{kwargs.get('strength_02')}_{kwargs.get('blocks_type_02')}_" \ + f"{kwargs.get('lora_03')}_{kwargs.get('strength_03')}_{kwargs.get('blocks_type_03')}_" \ + f"{kwargs.get('lora_04')}_{kwargs.get('strength_04')}_{kwargs.get('blocks_type_04')}" + def get_save_lora_path(filename_prefix, output_dir): def map_filename(filename): prefix_len = len(os.path.basename(filename_prefix)) @@ -5247,29 +5219,9 @@ class WarpedHunyuanLoraBatchMerge: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "merge_multiple_loras" - CATEGORY = "Warped/HunyuanTools" + CATEGORY = "Warped/Hunyuan/Merge" DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - def convert_key_format(self, key: str) -> str: - """Standardize LoRA key format by removing prefixes.""" - prefixes = ["diffusion_model.", "transformer."] - for prefix in prefixes: - if key.startswith(prefix): - key = key[len(prefix):] - break - return key - - def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: - """Filter LoRA weights based on block type.""" - if blocks_type == "all": - return lora - filtered_lora = {} - for key, value in lora.items(): - base_key = self.convert_key_format(key) - if blocks_type in base_key: - filtered_lora[key] = value - return filtered_lora - def load_lora(self, lora_name: str, strength: float, blocks_type: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: """Load and filter a single LoRA model.""" if not lora_name or strength == 0: @@ -5281,10 +5233,8 @@ class WarpedHunyuanLoraBatchMerge: raise ValueError(f"LoRA file not found: {lora_path}") # Load the LoRA weights - lora_weights = utils.load_torch_file(lora_path) - - # Filter the LoRA weights based on the block type - filtered_lora = self.filter_lora_keys(lora_weights, blocks_type) + lora_weights = convert_lora(lora_name, convert_to="diffusion_model") + filtered_lora = filter_lora_keys(lora_weights, blocks_type) return lora_weights, filtered_lora @@ -5307,29 +5257,19 @@ class WarpedHunyuanLoraBatchMerge: lora_weights, filtered_lora = self.load_lora(lora_2, 1.0, blocks_type_2) temp_loras["2"] = {"lora_weights": lora_weights, "strength": strength_2, "filtered_lora": filtered_lora} - loras = {} - - for lora_key in temp_loras.keys(): - loras[lora_key] = {"lora_weights": {}, "strength": temp_loras[lora_key]["strength"], "filtered_lora": temp_loras[lora_key]["filtered_lora"]} - - for key in temp_loras[lora_key]["lora_weights"].keys(): - new_key = key.replace("transformer.", "diffusion_model.") - loras[lora_key]["lora_weights"][new_key] = temp_loras[lora_key]["lora_weights"][key] - new_lora = {} - for lora_key in loras.keys(): - for key in loras[lora_key]["lora_weights"].keys(): + for lora_key in temp_loras.keys(): + for key in temp_loras[lora_key]["filtered_lora"].keys(): if not key in new_lora.keys(): new_lora[key] = None - # print("Lora: {} | Key: {} | Shape: {}".format(lora_key, key, loras[lora_key]["lora_weights"][key].shape)) # Merge The Weighted Key Weights for key in new_lora.keys(): - for lora_key in loras.keys(): - if key in loras[lora_key]["lora_weights"].keys(): + for lora_key in temp_loras.keys(): + if key in temp_loras[lora_key]["filtered_lora"].keys(): if not new_lora[key] is None: - temp_weights = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) + temp_weights = torch.mul(temp_loras[lora_key]["filtered_lora"][key], temp_loras[lora_key]["strength"]) if new_lora[key].shape[0] < new_lora[key].shape[1]: if temp_weights.shape[0] < new_lora[key].shape[0]: @@ -5367,7 +5307,7 @@ class WarpedHunyuanLoraBatchMerge: except Exception as e: raise(e) else: - new_lora[key] = torch.mul(loras[lora_key]["lora_weights"][key], loras[lora_key]["strength"]) + new_lora[key] = torch.mul(loras[lora_key]["filtered_lora"][key], loras[lora_key]["strength"]) if not save_metadata: metadata = None @@ -5402,60 +5342,8 @@ class WarpedHunyuanLoraConvertKeys: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "convert_lora" - CATEGORY = "Warped/HunyuanTools" - DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." - - def check_for_musubi(self, lora: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: - """Checks for and converts from Musubi Tuner format which supports Network Alpha and uses different naming. Largely copied from that project""" - prefix = "lora_unet_" - musubi = False - lora_alphas = {} - for key, value in lora.items(): - if key.startswith(prefix): - lora_name = key.split(".", 1)[0] # before first dot - if lora_name not in lora_alphas and "alpha" in key: - lora_alphas[lora_name] = value - musubi = True - if musubi: - print("Loading Musubi Tuner format LoRA...") - converted_lora = {} - for key, weight in lora.items(): - if key.startswith(prefix): - if "alpha" in key: - continue - lora_name = key.split(".", 1)[0] # before first dot - # HunyuanVideo lora name to module name: ugly but works - module_name = lora_name[len(prefix) :] # remove "lora_unet_" - module_name = module_name.replace("_", ".") # replace "_" with "." - module_name = module_name.replace("double.blocks.", "double_blocks.") # fix double blocks - module_name = module_name.replace("single.blocks.", "single_blocks.") # fix single blocks - module_name = module_name.replace("img.", "img_") # fix img - module_name = module_name.replace("txt.", "txt_") # fix txt - module_name = module_name.replace("attn.", "attn_") # fix attn - diffusers_prefix = "diffusion_model" - if "lora_down" in key: - new_key = f"{diffusers_prefix}.{module_name}.lora_A.weight" - dim = weight.shape[0] - elif "lora_up" in key: - new_key = f"{diffusers_prefix}.{module_name}.lora_B.weight" - dim = weight.shape[1] - else: - print(f"unexpected key: {key} in Musubi LoRA format") - continue - # scale weight by alpha - if lora_name in lora_alphas: - # we scale both down and up, so scale is sqrt - scale = lora_alphas[lora_name] / dim - scale = scale.sqrt() - weight = weight * scale - else: - print(f"missing alpha for {lora_name}") - - converted_lora[new_key] = weight - return converted_lora - else: - print("Loading Diffusers format LoRA...") - return lora + CATEGORY = "Warped/Hunyuan/Lora" + DESCRIPTION = "Convert Keys For Hunyuan LORA and Save Modified LORA." def load_lora(self, lora_name: str) -> Tuple[Dict[str, torch.Tensor],]: """Load and filter a single LoRA model.""" @@ -5477,42 +5365,7 @@ class WarpedHunyuanLoraConvertKeys: # Load the LoRA weights temp_lora = self.load_lora(lora) - temp_lora = self.check_for_musubi(temp_lora) - - new_lora = {} - - for key in temp_lora.keys(): - if key.startswith("transformer.") and (convert_to == "diffusion_model"): - new_key = key.replace("transformer.", "diffusion_model.") - new_lora[new_key] = temp_lora[key] - continue - - if key.startswith("diffusion_model.") and (convert_to == "transformer"): - new_key = key.replace("diffusion_model.", "transformer.") - new_lora[new_key] = temp_lora[key] - continue - - if key.startswith("lora_unet_"): - new_key = key.replace("lora_unet_", "{}.".format(convert_to)) - - if "double" in new_key: - new_key = new_key.replace("double_blocks_", "double_blocks.") - new_key = new_key.replace("_img_attn", ".img_attn") - new_key = new_key.replace("_img_mlp", ".img_mlp") - new_key = new_key.replace("_txt_attn", ".txt_attn") - new_key = new_key.replace("_txt_mlp", ".txt_mlp") - new_key = new_key.replace(".lora_up.", ".lora_A.") - new_key = new_key.replace(".lora_down.", ".lora_B.") - - continue - - if "single" in new_key: - new_key = new_key.replace("single_blocks_", "single_blocks.") - new_key = new_key.replace("_linear", ".linear") - new_key = new_key.replace(".lora_up.", ".lora_A.") - new_key = new_key.replace(".lora_down.", ".lora_B.") - - continue + new_lora = convert_lora(temp_lora, convert_to=convert_to) if not save_metadata: metadata = None @@ -5542,7 +5395,7 @@ class WarpedLoraKeysAndMetadataReader: RETURN_NAMES = ("keys", "metadata", ) OUTPUT_IS_LIST = (True, True, ) FUNCTION = "read_data" - CATEGORY = "Warped/Utils" + CATEGORY = "Warped/General/Lora" DESCRIPTION = "Read Metadata From Lora." def get_metadata(self, lora_path): @@ -5588,7 +5441,7 @@ class WarpedLoraKeysAndMetadataReader: lora_keys = [] for key in lora_weights.keys(): - lora_keys.append("{}".format(key)) + lora_keys.append("{} | Shape: {}\n".format(key, lora_weights[key].shape)) # print(key) lora_metadata = [] @@ -5605,7 +5458,6 @@ class WarpedLoraKeysAndMetadataReader: return { "ui": { "lora_keys": lora_keys, "lora_metadata": lora_metadata }, "result": (lora_keys, lora_metadata,), } - class WarpedHunyuanLoraConvert: def __init__(self): self.base_output_dir = get_default_output_folder() @@ -5626,8 +5478,8 @@ class WarpedHunyuanLoraConvert: OUTPUT_NODE = True OUTPUT_IS_LIST = (True,) FUNCTION = "convert_lora" - CATEGORY = "Warped/HunyuanTools" - DESCRIPTION = "Load and apply multiple LoRA models with different strengths and block types. Model input is required." + CATEGORY = "Warped/Hunyuan/Lora" + DESCRIPTION = "Convert Hunyuan Lora." def load_lora(self, lora_name: str) -> Tuple[Dict[str, torch.Tensor]]: if not lora_name: @@ -5649,6 +5501,7 @@ class WarpedHunyuanLoraConvert: if lora != "None": # Load and filter the LoRA weights lora_weights = self.load_lora(lora) + lora_weights = convert_lora(lora_weights, convert_to="diffusion_model") for key in lora_weights.keys(): sample_shape = lora_weights[key].shape @@ -5721,15 +5574,18 @@ class WarpedFramepackSampler: "positive": ("CONDITIONING",), "negative": ("CONDITIONING",), "steps": ("INT", {"default": 25, "min": 1}), - "use_teacache": ("BOOLEAN", {"default": True, "tooltip": "Use teacache for faster sampling."}), + "cache_mode": (["disabled", "use_teacache", "use_magcache"], {"default": "use_magcache", "tooltip": "Whether or not to use magcache or teacache for faster sampling."}), "teacache_rel_l1_thresh": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The threshold for the relative L1 loss."}), + "magcache_thresh": ("FLOAT", {"default": 0.1250, "min": 0.0000, "max": 0.3000, "step": 0.0010, "tooltip": "How strongly to cache the output of diffusion model. This value must be non-negative."}), + "magcache_retention_ratio": ("FLOAT", {"default": 0.225, "min": 0.100, "max": 0.300, "step": 0.001, "tooltip": "The start percentage of the steps that will apply MagCache."}), + "magcache_K": ("INT", {"default": 3, "min": 0, "max": 6, "step": 1, "tooltip": "The maxium skip steps of MagCache."}), "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 30.0, "step": 0.01}), "guidance_scale": ("FLOAT", {"default": 12.0, "min": 0.0, "max": 32.0, "step": 0.01}), "shift": ("FLOAT", {"default": 24.0, "min": 0.0, "max": 1000.0, "step": 0.01}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "preferred_frame_count": ("INT", {"default": 301, "min": 33, "max": 1000001, "step": 4, "tooltip": "For I2V and T2V, The total frames in the video. Disreguarded for V2V"}), "preferred_batch_size": ("INT", {"default": 61, "min": 37, "max": 161, "step": 4, "tooltip": "The preferred number of frames to use for sampling."}), "use_batch_size": (["next_lowest", "next_highest", "closest", "exact"], {"default": "next_lowest", "tooltip": "Number of frames generated may be impacted by choice."}), - "total_second_length": ("FLOAT", {"default": 10, "min": 1, "max": 3600, "step": 0.1, "tooltip": "For I2V and T2V, The total length of the video in seconds. Disreguarded for V2V"}), "gpu_memory_preservation": ("FLOAT", {"default": 12.0, "min": 0.0, "max": 128.0, "step": 0.1, "tooltip": "The amount of GPU memory to preserve."}), "sampler": (["unipc_bh1", "unipc_bh2"], { @@ -5746,46 +5602,58 @@ class WarpedFramepackSampler: "start_image": ("IMAGE", {"tooltip": "init image to use for image2video or video2video"} ), "end_image": ("IMAGE", {"tooltip": "end image to use for image2video"} ), "embed_interpolation": (["disabled", "weighted_average", "linear"], {"default": 'disabled', "tooltip": "Image embedding interpolation type. If linear, will smoothly interpolate with time, else it'll be weighted average with the specified weight."}), - "start_embed_strength": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Weighted average constant for image embed interpolation. If end image is not set, the embed's strength won't be affected"}), + "start_embed_strength": ("FLOAT", {"default": 1.20, "min": 0.00, "max": 2.00, "step": 0.01, "tooltip": "Weighted average constant for image embed interpolation. If end image is not set, the embed's strength won't be affected"}), + "secondary_embed_strength": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 2.00, "step": 0.01, "tooltip": "Weighted average constant for image embed interpolation. If end image is not set, the embed's strength won't be affected"}), "video_image_batch": ("IMAGE", {"tooltip": "init Latents to use for video2video"} ), - "fps": ("FLOAT", {"default": 30.00}), "denoise_strength": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 1.00, "step": 0.01}), "noise_strength": ("FLOAT", {"default": 1.00, "min": 0.10, "max": 1.00, "step": 0.01}), - "blend_frames": ("INT", {"default":1, "min":0, "max": 3, "step": 1}), - "t2v_width": ("INT", {"default":640, "min":256, "max": 1280, "step": 8}), - "t2v_height": ("INT", {"default":640, "min":256, "max": 1280, "step": 8}), + "blend_frames": ("INT", {"default":5, "min":0, "max": 16, "step": 1}), + "t2v_width": ("INT", {"default":480, "min":256, "max": 1280, "step": 16}), + "t2v_height": ("INT", {"default":720, "min":256, "max": 1280, "step": 16}), + "dummy_frames": ("INT", {"default":17, "min":17, "max": 121, "step": 4, "tooltip": "Number of frames to generate in dummy batch."}), + "gen_dummy": ("BOOLEAN", {"default": False, "tooltip": "For t2v or i2v only. Will generate a dummy batch to obtain a starting image for main generation."}), + "gen_dummy_only": ("BOOLEAN", {"default": False, "tooltip": "Will generate dummy batch only."}), + "dummy_cache_mode": (["disabled", "use_teacache", "use_magcache"], {"default": "use_magcache", "tooltip": "Whether or not to use magcache or teacache on dummy generation for faster sampling."}), + "use_dummy_image": (["first", "middle", "last", "random", "all"], {"default": "last", "tooltip": "Which dummy batch image to start main generation."}), "v2v_context_count": ("INT", {"default":5, "min":3, "max": 10, "step": 1}), "verbose_messaging": ("BOOLEAN", {"default": False}), } } - RETURN_TYPES = ("IMAGE", "STRING", "FLOAT", ) - RETURN_NAMES = ("images", "generation_status", "fps", ) + RETURN_TYPES = ("IMAGE", "STRING", "INT", ) + RETURN_NAMES = ("images", "generation_status", "seed", ) FUNCTION = "process" CATEGORY = "Warped/Framepack/Sampling" - def process(self, model, vae, shift, positive, negative, preferred_batch_size, use_batch_size, use_teacache, total_second_length, teacache_rel_l1_thresh, steps, cfg, + def process(self, model, vae, shift, positive, negative, preferred_frame_count, preferred_batch_size, use_batch_size, cache_mode, teacache_rel_l1_thresh, + magcache_thresh, magcache_retention_ratio, magcache_K, steps, cfg, guidance_scale, seed, sampler, dec_tile_size, dec_overlap, dec_temporal_size, dec_temporal_overlap, skip_frames, clip_vision_model, - gpu_memory_preservation, start_image=None, end_image=None, embed_interpolation="linear", start_embed_strength=1.0, video_image_batch=None, - fps=30.00, denoise_strength=1.00, noise_strength=1.00, blend_frames=0, t2v_width=640, t2v_height=640, v2v_context_count=5, verbose_messaging=False): + gpu_memory_preservation, start_image=None, end_image=None, embed_interpolation="linear", start_embed_strength=1.0, secondary_embed_strength=1.0, video_image_batch=None, + denoise_strength=1.00, noise_strength=1.00, blend_frames=0, t2v_width=640, t2v_height=640, dummy_frames=5, gen_dummy=False, gen_dummy_only=False, dummy_cache_mode="disabled", use_dummy_image="last", + v2v_context_count=5, verbose_messaging=False): self.dec_tile_size = dec_tile_size self.dec_overlap = dec_overlap self.dec_temporal_size = dec_temporal_size self.dec_temporal_overlap = dec_temporal_overlap self.skip_frames = skip_frames self.vae = vae - self.total_second_length = total_second_length + self.preferred_frame_count = preferred_frame_count self.seed = seed self.steps = steps self.cfg = cfg - self.use_teacache = use_teacache + self.use_teacache = False + self.use_magcache = False self.teacache_rel_l1_thresh = teacache_rel_l1_thresh + self.magcache_thresh = magcache_thresh + self.magcache_retention_ratio = magcache_retention_ratio + self.magcache_K = magcache_K self.guidance_scale = guidance_scale self.sampler = sampler self.skip_frames = skip_frames self.gpu_memory_preservation = gpu_memory_preservation self.embed_interpolation = embed_interpolation self.start_embed_strength = start_embed_strength + self.secondary_embed_strength = secondary_embed_strength self.denoise_strength = denoise_strength self.noise_strength = noise_strength self.transformer = model["transformer"] @@ -5799,53 +5667,87 @@ class WarpedFramepackSampler: self.shift = shift self.t2v_width=t2v_width self.t2v_height=t2v_height - self.fps = fps + self.dummy_latents = int((dummy_frames - 1) // 4) + 1 + self.gen_dummy = gen_dummy + self.gen_dummy_only = gen_dummy_only + self.dummy_teacache = False + self.dummy_magcache = False + self.use_dummy_image = use_dummy_image self.v2v_context_count = v2v_context_count self.verbose_messaging = verbose_messaging + if cache_mode == "use_teacache": + self.use_teacache = True + + if cache_mode == "use_magcache": + self.use_magcache = True + + if dummy_cache_mode == "use_teacache": + self.dummy_teacache = True + + if dummy_cache_mode == "use_magcache": + self.dummy_magcache = True + print("Device: {} | Offload Device: {}".format(self.device, self.offload_device)) + self.llama_vec = self.positive[0][0].to(self.base_dtype).to(self.device) + self.clip_l_pooler = self.positive[0][1]["pooled_output"].to(self.base_dtype).to(self.device) + + if not math.isclose(self.cfg, 1.0): + self.llama_vec_n = self.negative[0][0].to(self.base_dtype) + self.clip_l_pooler_n = self.negative[0][1]["pooled_output"].to(self.base_dtype).to(self.device) + else: + self.llama_vec_n = torch.zeros_like(self.llama_vec, device=self.device) + self.clip_l_pooler_n = torch.zeros_like(self.clip_l_pooler, device=self.device) + + self.llama_vec, self.llama_attention_mask = crop_or_pad_yield_mask(self.llama_vec, length=512) + self.llama_vec_n, self.llama_attention_mask_n = crop_or_pad_yield_mask(self.llama_vec_n, length=512) + if not video_image_batch is None: if len(video_image_batch.shape) < 4: video_image_batch = video_image_batch.unsqueeze(0) - self.latent_window_size, self.batch_count, truncated_frame_count = self.get_latent_window_size(preferred_batch_size, video_image_batch.shape[0], use_batch_size=use_batch_size) - self.total_second_length = truncated_frame_count / self.fps + self.preferred_frame_count = video_image_batch.shape[0] + + self.latent_window_size, self.batch_count, truncated_frame_count = self.get_latent_window_size(preferred_batch_size, self.preferred_frame_count, use_batch_size=use_batch_size) if video_image_batch.shape[0] != truncated_frame_count: image_batches_tuple = torch.split(video_image_batch, truncated_frame_count, dim=0) image_batches_split = [item for item in image_batches_tuple] video_image_batch = image_batches_split[0] else: - frame_count = int(((total_second_length * self.fps) / 4) * 4) + 1 - print("frame_count: {}".format(frame_count)) - self.latent_window_size, self.batch_count, truncated_frame_count = self.get_latent_window_size(preferred_batch_size, frame_count, use_batch_size=use_batch_size) - self.total_second_length = truncated_frame_count / self.fps + print("preferred_frame_count: {}".format(self.preferred_frame_count)) + self.latent_window_size, self.batch_count, truncated_frame_count = self.get_latent_window_size(preferred_batch_size, self.preferred_frame_count, use_batch_size=use_batch_size) + self.num_frames = self.encoded_to_decoded_length(self.latent_window_size) self.total_frames = truncated_frame_count - mm.unload_all_models() - mm.cleanup_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + if self.verbose_messaging: + print("num_frames: {}".format(self.num_frames)) + + self.cleanup(unload_models=True, cleanup_models=True, cleanup_cuda=True) if not video_image_batch is None: + self.mode = VideoGenerationType.V2V + return self.process_v2v(video_image_batch) if not start_image is None: - return self.process_i2v(start_image, end_image, is_i2v=True) + self.mode = VideoGenerationType.I2V + return self.process_i2v(start_image, end_image) + + self.mode = VideoGenerationType.T2V return self.process_t2v() def process_t2v(self): - return self.process_i2v(None, None, is_i2v=False) + return self.process_i2v(None, None) - def process_i2v(self, start_image, end_image, is_i2v=True): + def process_i2v(self, start_image, end_image): start_latent = None end_latent = None - if is_i2v: + if self.mode == VideoGenerationType.I2V: if not start_image is None: image_embeds = self.clip_vision_encode(start_image) start_latent = self.encode_batched(start_image, per_batch=self.latent_window_size) @@ -5884,9 +5786,8 @@ class WarpedFramepackSampler: if self.verbose_messaging: print("process_i2v: Image Width: {}".format(self.width)) - print("process_i2v: Image Width: {}".format(self.width)) + print("process_i2v: Image Height: {}".format(self.height)) print("process_i2v: total_frames: {}".format(self.total_frames)) - print("process_i2v: total_second_length: {}".format(self.total_second_length)) print("process_i2v: batch_count: {}".format(self.batch_count)) print("process_i2v: latent_window_size: {}".format(self.latent_window_size)) @@ -5898,95 +5799,83 @@ class WarpedFramepackSampler: else: end_image_encoder_last_hidden_state = torch.zeros_like(start_image_encoder_last_hidden_state) - llama_vec = self.positive[0][0].to(self.base_dtype).to(self.device) - clip_l_pooler = self.positive[0][1]["pooled_output"].to(self.base_dtype).to(self.device) - - if not math.isclose(self.cfg, 1.0): - llama_vec_n = self.negative[0][0].to(self.base_dtype) - clip_l_pooler_n = self.negative[0][1]["pooled_output"].to(self.base_dtype).to(self.device) - else: - llama_vec_n = torch.zeros_like(llama_vec, device=self.device) - clip_l_pooler_n = torch.zeros_like(clip_l_pooler, device=self.device) - - llama_vec, llama_attention_mask = crop_or_pad_yield_mask(llama_vec, length=512) - llama_vec_n, llama_attention_mask_n = crop_or_pad_yield_mask(llama_vec_n, length=512) - rnd = torch.Generator("cpu").manual_seed(self.seed) - - num_frames = self.encoded_to_decoded_length(self.latent_window_size) - - if self.verbose_messaging: - print("num_frames: {}".format(num_frames)) - total_generated_latent_frames = 0 - if is_i2v: + if self.mode == VideoGenerationType.I2V: if start_latent is None: raise ValueError("A start_image value is required for I2V.") cat_list = [] history_latents = torch.zeros(size=(1, 16, 19, H, W), dtype=torch.float32).cpu() + original_history_latents = history_latents.clone().detach() - cat_list.append(start_latent.to(history_latents)) cat_list.append(history_latents) + cat_list.append(start_latent.to(history_latents)) history_latents = torch.cat(cat_list, dim=2) else: history_latents = torch.zeros(size=(1, 16, 20, H, W), dtype=torch.float32).cpu() - - original_history_latents = history_latents.clone().detach() + original_history_latents = torch.zeros(size=(1, 16, 19, H, W), dtype=torch.float32).cpu() latent_paddings_list = list(range(total_latent_sections)) latent_paddings = latent_paddings_list.copy() # Create a copy for iteration - comfy_model = HyVideoModel( - HyVideoModelConfig(self.base_dtype), - model_type=comfy.model_base.ModelType.FLOW, - device=self.device, - ) - - patcher = comfy.model_patcher.ModelPatcher(comfy_model, self.device, torch.device("cpu")) - callback = latent_preview.prepare_callback(patcher, self.steps) - - move_model_to_device_with_memory_preservation(self.transformer, target_device=self.device, preserved_memory_gb=self.gpu_memory_preservation) - - if self.verbose_messaging: - print("latent_paddings: {}".format(latent_paddings)) + comfy_model, patcher, callback = self.initialize_comfy_model() latent_batches_gend = [] - temp_noise = torch.randn((int(llama_vec.shape[0]), 16, self.latent_window_size * self.batch_count, H, W), generator=rnd, device=rnd.device).to(device=rnd.device, dtype=torch.float32) + is_dummy_section = False + was_dummy_used = False - if Decimal(self.noise_strength).compare(Decimal(1.00)) != 0: - temp_noise = torch.mul(temp_noise, self.noise_strength) + noise, dummy_noise = self.setup_latent_noise(self.latent_window_size * self.batch_count, W, H) - temp_noise_tuple = torch.split(temp_noise, self.latent_window_size, dim=2) - noise = [item for item in temp_noise_tuple] + if self.gen_dummy: + # dummy_noise = self.setup_dummy_noise(W, H) + + if not self.gen_dummy_only: + latent_paddings.insert(0, 0) + else: + latent_paddings = [0] + latent_paddings_list = latent_paddings.copy() + total_latent_sections = 1 + + is_dummy_section = True + dummy_frame_count = ((self.dummy_latents - 1) * 4) + 1 + + if self.verbose_messaging: + print("latent_paddings: {}".format(latent_paddings)) + print("noise batches length: {}".format(len(noise))) - temp_noise = None - temp_noise_tuple = None generated_latents = None interrupted = False real_history_latents = None context_latents = None generation_status = "" temp_history_latents = None + dummy_images = None + dummy_gen_latents = None + generated_image_batches = [] - mm.unload_all_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) try: for i, latent_padding in enumerate(latent_paddings): - is_first_section = latent_padding == min(latent_paddings) - is_last_section = latent_padding == max(latent_paddings) + if not self.gen_dummy_only: + is_first_section = latent_padding == min(latent_paddings) + is_last_section = latent_padding == max(latent_paddings) + else: + is_first_section = True + is_last_section = True if self.verbose_messaging: print("history_latents Shape: {}".format(history_latents.shape)) - noise_latent = noise[latent_padding] + if not is_dummy_section: + noise_latent = noise[latent_padding] + else: + noise_latent = dummy_noise latent_padding_size = latent_padding * self.latent_window_size @@ -5998,23 +5887,32 @@ class WarpedFramepackSampler: if total_latent_sections <= 1: frac = 1.0 # Handle case with only one section else: - frac = 1 - (i / (total_latent_sections - 1)) # going backwards + frac = 1 - (latent_padding / (total_latent_sections - 1)) # going backwards else: frac = self.start_embed_strength if has_end_image else 1.0 image_encoder_last_hidden_state = ((start_image_encoder_last_hidden_state * frac) + ((1 - frac) * end_image_encoder_last_hidden_state)) * self.start_embed_strength else: - image_encoder_last_hidden_state = start_image_encoder_last_hidden_state * self.start_embed_strength + if is_dummy_section or (is_first_section and not self.gen_dummy): + image_encoder_last_hidden_state = start_image_encoder_last_hidden_state * self.start_embed_strength + else: + image_encoder_last_hidden_state = start_image_encoder_last_hidden_state * self.secondary_embed_strength start_latent_frames = T # 0 or 1 if self.verbose_messaging: - print(f'latent_padding_size = {latent_padding_size}, is_last_section = {is_last_section}, is_first_section = {is_first_section}') + print(f'latent_padding_size = {latent_padding_size}, is_last_section = {is_last_section}, is_first_section = {is_first_section}, is_dummy_section = {is_dummy_section}') - indices = torch.arange(0, sum([1, 16, 2, 1, self.latent_window_size])).unsqueeze(0) - clean_latent_indices_start, clean_latent_4x_indices, clean_latent_2x_indices, clean_latent_1x_indices, latent_indices = indices.split([1, 16, 2, 1, self.latent_window_size], dim=1) - clean_latent_indices = torch.cat([clean_latent_indices_start, clean_latent_1x_indices], dim=1) - clean_latents_4x, clean_latents_2x, clean_latents_1x = history_latents[:, :, -sum([16, 2, 1]):, :, :].split([16, 2, 1], dim=2) + if not is_dummy_section: + indices = torch.arange(0, sum([1, 16, 2, 1, self.latent_window_size])).unsqueeze(0) + clean_latent_indices_start, clean_latent_4x_indices, clean_latent_2x_indices, clean_latent_1x_indices, latent_indices = indices.split([1, 16, 2, 1, self.latent_window_size], dim=1) + clean_latent_indices = torch.cat([clean_latent_indices_start, clean_latent_1x_indices], dim=1) + clean_latents_4x, clean_latents_2x, clean_latents_1x = history_latents[:, :, -sum([16, 2, 1]):, :, :].split([16, 2, 1], dim=2) + else: + indices = torch.arange(0, sum([1, 16, 2, 1, self.dummy_latents])).unsqueeze(0) + clean_latent_indices_start, clean_latent_4x_indices, clean_latent_2x_indices, clean_latent_1x_indices, latent_indices = indices.split([1, 16, 2, 1, self.dummy_latents], dim=1) + clean_latent_indices = torch.cat([clean_latent_indices_start, clean_latent_1x_indices], dim=1) + clean_latents_4x, clean_latents_2x, clean_latents_1x = history_latents[:, :, -sum([16, 2, 1]):, :, :].split([16, 2, 1], dim=2) if is_last_section and (not end_latent is None): clean_latents = torch.cat([start_latent.to(history_latents), end_latent.to(history_latents)], dim=2) @@ -6033,33 +5931,140 @@ class WarpedFramepackSampler: print("clean_latents_2x Shape: {}".format(clean_latents_2x.shape)) print("clean_latents_4x Shape: {}".format(clean_latents_4x.shape)) print("clean_latents Shape: {}\n".format(clean_latents.shape)) - print("noise Shape: {}\n".format(noise[latent_padding].shape)) + print("noise Shape: {}\n".format(noise_latent.shape)) - if self.use_teacache: - self.transformer.initialize_teacache(enable_teacache=True, num_steps=self.steps, rel_l1_thresh=self.teacache_rel_l1_thresh) - else: + if (self.use_teacache and not is_dummy_section) or (is_dummy_section and self.dummy_teacache): + self.transformer.__class__.forward = self.transformer.orig_forward + self.transformer.initialize_teacache(enable_teacache=True, num_steps=self.steps, rel_l1_thresh=self.teacache_rel_l1_thresh, verbose_messaging=self.verbose_messaging) + self.transformer.initialize_magcache(enable_magcache=False) + + if self.verbose_messaging: + print("TeaCache Enabled") + elif (self.use_magcache and not is_dummy_section) or (is_dummy_section and self.dummy_magcache): + self.transformer.__class__.forward = self.transformer.__class__.magcache_framepack_forward + self.transformer.initialize_magcache(enable_magcache=True, num_steps=self.steps, magcache_thresh=self.magcache_thresh, K=self.magcache_K, retention_ratio=self.magcache_retention_ratio, verbose_messaging=self.verbose_messaging) self.transformer.initialize_teacache(enable_teacache=False) - print("Generating Batch {}".format(latent_padding)) + if self.verbose_messaging: + print("MagCache Enabled") + else: + self.transformer.__class__.forward = self.transformer.orig_forward + self.transformer.initialize_teacache(enable_teacache=False) + self.transformer.initialize_magcache(enable_magcache=False) - generated_latents = self.generate_video(W * 8, H * 8, num_frames, rnd, noise_latent, llama_vec, llama_attention_mask, clip_l_pooler, llama_vec_n, llama_attention_mask_n, clip_l_pooler_n, image_encoder_last_hidden_state, - latent_indices, clean_latents, clean_latent_indices, clean_latents_2x, clean_latent_2x_indices, clean_latents_4x, clean_latent_4x_indices, callback) + if self.verbose_messaging: + print("Both TeaCache and MagCache Disabled") - noise[latent_padding] = None + if not is_dummy_section: + print("Generating Batch {}".format(latent_padding)) + else: + print("Generating Dummy Batch.") + + generated_latents = self.generate_video(W * 8, H * 8, self.num_frames if not is_dummy_section else dummy_frame_count, rnd, noise_latent, self.llama_vec, self.llama_attention_mask, self.clip_l_pooler, self.llama_vec_n, + self.llama_attention_mask_n, self.clip_l_pooler_n, image_encoder_last_hidden_state, latent_indices, clean_latents, clean_latent_indices, clean_latents_2x, clean_latent_2x_indices, + clean_latents_4x, clean_latent_4x_indices, callback) + if not is_dummy_section: + noise[latent_padding] = None + else: + dummy_noise = None if self.verbose_messaging: print("generated_latents {} | Shape: {}".format(latent_padding, generated_latents.shape)) - if not is_last_section: - history_latents = torch.cat([original_history_latents, generated_latents.to(original_history_latents)], dim=2) + if not is_dummy_section: + offload_model_from_device_for_memory_preservation(self.transformer, self.device, preserved_memory_gb=0) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) - latent_batches_gend.append(generated_latents.clone().detach() / vae_scaling_factor) + decoded_images = partial_decode_tiled(self.vae, generated_latents.clone().detach() / vae_scaling_factor, self.dec_tile_size, self.dec_overlap, self.dec_temporal_size, self.dec_temporal_overlap) + generated_image_batches.append(decoded_images.clone().detach()) + + decoded_tuple = torch.split(decoded_images, 1, dim=0) + decoded_split = [item for item in decoded_tuple] + decoded_images = decoded_images.to(get_offload_device()) + decoded_images = None + + start_image = decoded_split[len(decoded_split) - 1] + image_embeds = self.clip_vision_encode(start_image) + start_latent = self.encode_batched(start_image, per_batch=self.latent_window_size) + + start_latent = start_latent["samples"] * vae_scaling_factor + + if self.verbose_messaging: + print("start_latent Shape: {}".format(start_latent.shape)) + + start_image_encoder_last_hidden_state = image_embeds["last_hidden_state"].to(self.base_dtype).to(self.device) + history_latents = torch.cat([start_latent.to(original_history_latents), original_history_latents], dim=2) + + decoded_tuple = None + decoded_split = None + + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) + + if not is_last_section: + history_latents = torch.cat([original_history_latents, generated_latents.to(original_history_latents)], dim=2) + move_model_to_device_with_memory_preservation(self.transformer, target_device=self.device, preserved_memory_gb=self.gpu_memory_preservation) + + # latent_batches_gend.append(generated_latents.clone().detach() / vae_scaling_factor) + + if self.verbose_messaging: + print("history_latents {} | Shape: {}".format(latent_padding, history_latents.shape)) + + total_generated_latent_frames += int(generated_latents.shape[2]) + else: + offload_model_from_device_for_memory_preservation(self.transformer, self.device, preserved_memory_gb=0) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) + + dummy_gen_latents = generated_latents.clone().detach() / vae_scaling_factor + dummy_decoded = partial_decode_tiled(self.vae, dummy_gen_latents, self.dec_tile_size, self.dec_overlap, self.dec_temporal_size, self.dec_temporal_overlap) + + if len(dummy_decoded) < 4: + dummy_decoded = dummy_decoded.unsqueeze(0) + + dummy_images = dummy_decoded.clone().detach() + + if self.use_dummy_image == "all": + generated_image_batches.append(dummy_images) + + if not self.gen_dummy_only: + dummy_tuple = torch.split(dummy_decoded, 1, dim=0) + dummy_split = [item for item in dummy_tuple] + dummy_decoded = dummy_decoded.to(get_offload_device()) + dummy_decoded = None + + if (self.use_dummy_image == "last") or (self.use_dummy_image == "all"): + start_image = dummy_split[len(dummy_split) - 1] + elif self.use_dummy_image == "first": + start_image = dummy_split[0] + elif self.use_dummy_image == "middle": + start_image = dummy_split[int(math.ceil(len(dummy_split) / 2))] + else: + random.seed(self.seed) + start_image = dummy_split[random.randrange(0, len(dummy_split) - 1, 1)] + + image_embeds = self.clip_vision_encode(start_image) + start_latent = self.encode_batched(start_image, per_batch=self.latent_window_size) + + start_latent = start_latent["samples"] * vae_scaling_factor + print("start_latent Shape: {}".format(start_latent.shape)) + + start_image_encoder_last_hidden_state = image_embeds["last_hidden_state"].to(self.base_dtype).to(self.device) + history_latents = torch.cat([start_latent.to(original_history_latents), original_history_latents], dim=2) + + is_dummy_section = False + dummy_tuple = None + dummpy_split = None + dummy_decoded = None + + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) + + if not self.gen_dummy_only: + move_model_to_device_with_memory_preservation(self.transformer, target_device=self.device, preserved_memory_gb=self.gpu_memory_preservation) + + generated_latents = None if self.verbose_messaging: print("history_latents {} | Shape: {}".format(latent_padding, history_latents.shape)) - total_generated_latent_frames += int(generated_latents.shape[2]) - if is_last_section: break except mm.InterruptProcessingException as ie: @@ -6071,10 +6076,7 @@ class WarpedFramepackSampler: pass except BaseException as e: - mm.unload_all_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) print(f"\nWarpedFramepackSampler: Exception During Processing: {str(e)}") print("WarpedFramepackSampler: Returning only partial results (if any).\n If zero images generated, a blank red image will be returned.\n") @@ -6107,17 +6109,17 @@ class WarpedFramepackSampler: video_latent_batches = None video_image_batch = None - # self.transformer.to(self.offload_device) - + # if not self.gen_dummy_only: offload_model_from_device_for_memory_preservation(self.transformer, self.device, preserved_memory_gb=0) - mm.unload_all_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) - if len(latent_batches_gend) > 0: - output_images = self.decode_batches(latent_batches_gend, self.skip_frames) + if (len(generated_image_batches) > 0): + output_images = self.assemble_final_result(generated_image_batches) + elif not dummy_images is None: + output_images = dummy_images + elif not dummy_gen_latents is None: + output_images = partial_decode_tiled(self.vae, dummy_gen_latents, self.dec_tile_size, self.dec_overlap, self.dec_temporal_size, self.dec_temporal_overlap) elif interrupted: temp_image = Image.new('RGB', (self.width, self.height), color = 'yellow') output_images = pil2tensorSwap(temp_image) @@ -6132,7 +6134,7 @@ class WarpedFramepackSampler: if self.verbose_messaging: print("output_images Shape: {}".format(output_images.shape)) - return (output_images, generation_status, self.fps, ) + return (output_images, generation_status, self.seed, ) def process_v2v(self, video_image_batch): if len(video_image_batch.shape) < 4: @@ -6152,7 +6154,6 @@ class WarpedFramepackSampler: print("process_v2v: Video Context Images Shape: {}".format(video_image_batch.shape)) print("process_v2v: image_batch_size: {}".format(image_batch_size)) print("process_v2v: latent_batch_size: {}".format(latent_batch_size)) - print("process_v2v: Total Seconds Length: {}".format(self.total_second_length)) total_latent_sections = self.batch_count @@ -6163,21 +6164,8 @@ class WarpedFramepackSampler: B, C, _, H, W = video_latent_batches[0].shape T = 1 - llama_vec = self.positive[0][0].to(self.base_dtype).to(self.device) - clip_l_pooler = self.positive[0][1]["pooled_output"].to(self.base_dtype).to(self.device) - - if not math.isclose(self.cfg, 1.0): - llama_vec_n = self.negative[0][0].to(self.base_dtype) - clip_l_pooler_n = self.negative[0][1]["pooled_output"].to(self.base_dtype).to(self.device) - else: - llama_vec_n = torch.zeros_like(llama_vec, device=self.device) - clip_l_pooler_n = torch.zeros_like(clip_l_pooler, device=self.device) - - llama_vec, llama_attention_mask = crop_or_pad_yield_mask(llama_vec, length=512) - llama_vec_n, llama_attention_mask_n = crop_or_pad_yield_mask(llama_vec_n, length=512) - output_images = None - num_frames = self.encoded_to_decoded_length(self.latent_window_size) + latent_paddings_list = list(range(total_latent_sections)) total_generated_latent_frames = 0 @@ -6185,35 +6173,17 @@ class WarpedFramepackSampler: latent_embeds = self.get_video_latent_embeds(video_image_batch, image_batch_size, latent_paddings) - comfy_model = HyVideoModel( - HyVideoModelConfig(self.base_dtype), - model_type=comfy.model_base.ModelType.FLOW, - device=self.device, - ) - - patcher = comfy.model_patcher.ModelPatcher(comfy_model, self.device, torch.device("cpu")) - callback = latent_preview.prepare_callback(patcher, self.steps) - - move_model_to_device_with_memory_preservation(self.transformer, target_device=self.device, preserved_memory_gb=self.gpu_memory_preservation) + comfy_model, patcher, callback = self.initialize_comfy_model() print("latent_paddings count: {} | latent_paddings: {}".format(len(latent_paddings), latent_paddings)) - latent_batches_gend = [] - - temp_noise = torch.randn((int(llama_vec.shape[0]), 16, self.latent_window_size * self.batch_count, H, W), generator=rnd, device=rnd.device).to(device=rnd.device, dtype=torch.float32) - - if Decimal(self.noise_strength).compare(Decimal(1.00)) != 0: - temp_noise = torch.mul(temp_noise, self.noise_strength) - - temp_noise_tuple = torch.split(temp_noise, self.latent_window_size, dim=2) - noise = [item for item in temp_noise_tuple] + # noise = self.setup_latent_noise(W, H) + noise, _ = self.setup_latent_noise(self.latent_window_size * self.batch_count, W, H) if self.verbose_messaging: print("Noise Batches Length: {}".format(len(noise))) print("sample noise Shape: {}".format(noise[0].shape)) - temp_noise = None - temp_noise_tuple = None has_end_image = True generated_latents = None interrupted = False @@ -6224,15 +6194,13 @@ class WarpedFramepackSampler: temp_history_latents = None original_history_latents = None - mm.unload_all_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) + + latent_batches_gend = [] try: for padding_i, latent_padding in enumerate(latent_paddings): padding_key = "{}".format(latent_padding) - prev_padding_key = "{}".format(latent_padding - 1) next_padding_key = "{}".format(latent_padding + 1) is_first_section = latent_padding == min(latent_paddings) @@ -6243,12 +6211,12 @@ class WarpedFramepackSampler: image_encoder_last_hidden_state = torch.mul(start_image_encoder_last_hidden_state, self.start_embed_strength) if self.verbose_messaging: - print("latent_padding: {} | latent_window_size: {}".format(latent_padding, self.latent_window_size)) + print("\nlatent_padding: {} | latent_window_size: {}".format(latent_padding, self.latent_window_size)) noise_latent = noise[latent_padding] if self.verbose_messaging: - print("noise_latent Shape: ()".format(noise_latent.shape)) + print("noise_latent Shape: {}".format(noise_latent.shape)) if is_first_section: original_history_latents = torch.zeros(size=(1, 16, self.buffer_length, H, W), dtype=torch.float32).cpu() @@ -6269,27 +6237,40 @@ class WarpedFramepackSampler: if self.v2v_context_count > history_latents.shape[2]: self.v2v_context_count = history_latents.shape[2] - num_frames = self.encoded_to_decoded_length(self.latent_window_size) - if self.verbose_messaging: print("history_latents Shape: {}\n".format(history_latents.shape)) - print("indices: {}".format(indices)) print("latent_indices: {}\n".format(latent_indices)) print("clean_latent_2x_indices: {}".format(clean_latent_2x_indices)) print("clean_latent_4x_indices: {}".format(clean_latent_4x_indices)) print("clean_latent_indices: {}\n".format(clean_latent_indices)) - print("clean_latents_2x Shape: {}".format(clean_latents_2x.shape)) print("clean_latents_4x Shape: {}".format(clean_latents_4x.shape)) print("clean_latents Shape: {}\n".format(clean_latents.shape)) print("noise Shape: {}\n".format(noise[latent_padding].shape)) - if self.use_teacache: - self.transformer.initialize_teacache(enable_teacache=True, num_steps=self.steps, rel_l1_thresh=self.teacache_rel_l1_thresh) - else: + if (self.use_teacache and not is_dummy_section) or (is_dummy_section and self.dummy_teacache): + self.transformer.__class__.forward = self.transformer.orig_forward + self.transformer.initialize_teacache(enable_teacache=True, num_steps=self.steps, rel_l1_thresh=self.teacache_rel_l1_thresh, verbose_messaging=self.verbose_messaging) + self.transformer.initialize_magcache(enable_magcache=False) + + if self.verbose_messaging: + print("TeaCache Enabled") + elif (self.use_magcache and not is_dummy_section) or (is_dummy_section and self.dummy_magcache): + self.transformer.__class__.forward = self.transformer.__class__.magcache_framepack_forward + self.transformer.initialize_magcache(enable_magcache=True, num_steps=self.steps, magcache_thresh=self.magcache_thresh, K=self.magcache_K, retention_ratio=self.magcache_retention_ratio, verbose_messaging=self.verbose_messaging) self.transformer.initialize_teacache(enable_teacache=False) + if self.verbose_messaging: + print("MagCache Enabled") + else: + self.transformer.__class__.forward = self.transformer.orig_forward + self.transformer.initialize_teacache(enable_teacache=False) + self.transformer.initialize_magcache(enable_magcache=False) + + if self.verbose_messaging: + print("Both TeaCache and MagCache Disabled") + clean_latents.to(dtype=torch.float32, device=self.device) clean_latents_2x.to(dtype=torch.float32, device=self.device) clean_latents_4x.to(dtype=torch.float32, device=self.device) @@ -6297,9 +6278,9 @@ class WarpedFramepackSampler: print("Generating Batch {}".format(latent_padding)) - generated_latents = self.generate_video(W * 8, H * 8, num_frames, rnd, noise_latent, llama_vec, llama_attention_mask, clip_l_pooler, llama_vec_n, llama_attention_mask_n, clip_l_pooler_n, image_encoder_last_hidden_state, - latent_indices, clean_latents, clean_latent_indices, clean_latents_2x, clean_latent_2x_indices, clean_latents_4x, clean_latent_4x_indices, callback) - + generated_latents = self.generate_video(W * 8, H * 8, self.num_frames, rnd, noise_latent, self.llama_vec, self.llama_attention_mask, self.clip_l_pooler, self.llama_vec_n, self.llama_attention_mask_n, + self.clip_l_pooler_n, image_encoder_last_hidden_state, latent_indices, clean_latents, clean_latent_indices, clean_latents_2x, clean_latent_2x_indices, + clean_latents_4x, clean_latent_4x_indices, callback) noise[latent_padding] = None if self.verbose_messaging: @@ -6345,10 +6326,7 @@ class WarpedFramepackSampler: pass except BaseException as e: - mm.unload_all_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) print(f"\nWarpedFramepackSampler: Exception During Processing: {str(e)}") print("WarpedFramepackSampler: Returning only partial results (if any).\n If zero images generated, a blank red image will be returned.\n") @@ -6385,13 +6363,10 @@ class WarpedFramepackSampler: # self.transformer.to(self.offload_device) offload_model_from_device_for_memory_preservation(self.transformer, self.device, preserved_memory_gb=0) - mm.unload_all_models() - mm.soft_empty_cache() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=True) if len(latent_batches_gend) > 0: - output_images = self.decode_batches(latent_batches_gend, self.skip_frames, is_v2v=True) + output_images = self.decode_batches(latent_batches_gend, self.skip_frames) elif interrupted: temp_image = Image.new('RGB', (self.width, self.height), color = 'yellow') output_images = pil2tensorSwap(temp_image) @@ -6407,7 +6382,7 @@ class WarpedFramepackSampler: if self.verbose_messaging: print("output_images Shape: {}".format(output_images.shape)) - return (output_images, generation_status, self.fps, ) + return (output_images, generation_status, self.seed, ) def generate_video(self, width, height, num_frames, seed_generator, noise_latent, llama_vec, llama_attention_mask, clip_l_pooler, llama_vec_n, llama_attention_mask_n, clip_l_pooler_n, image_encoder_last_hidden_state, latent_indices, clean_latents, clean_latent_indices, clean_latents_2x, clean_latent_2x_indices, clean_latents_4x, clean_latent_4x_indices, callback): @@ -6448,6 +6423,81 @@ class WarpedFramepackSampler: return generated_latents + def setup_latent_noise(self, noise_latent_length, width, height): + rnd = torch.Generator("cpu").manual_seed(self.seed) + dummy_noise = None + + if not self.gen_dummy or (self.mode == VideoGenerationType.V2V): + temp_noise = torch.randn((1, 16, noise_latent_length, height, width), generator=rnd, device=rnd.device).to(device=rnd.device, dtype=torch.float32) + else: + dummy_noise = torch.randn((1, 16, self.dummy_latents, height, width), generator=rnd, device=rnd.device).to(device=rnd.device, dtype=torch.float32) + + rnd = torch.Generator("cpu").manual_seed(self.seed) + temp_noise = torch.randn((1, 16, noise_latent_length + self.dummy_latents - 1, height, width), generator=rnd, device=rnd.device).to(device=rnd.device, dtype=torch.float32) + + temp_noise_tuple = torch.split(temp_noise, self.dummy_latents, dim=2) + temp_noise_split = [item for item in temp_noise_tuple] + temp_noise_tuple = None + + # dummy_noise = temp_noise_split[0] + + dummy_noise_tuple = torch.split(dummy_noise, 1, dim=2) + dummy_noise_split = [item for item in dummy_noise_tuple] + dummy_noise_tuple = None + + temp_noise = dummy_noise_split[len(dummy_noise_split) - 1] + dummy_noise_split = None + + i = -1 + for entry in temp_noise_split: + i += 1 + + if i > 0: + if not temp_noise is None: + temp_noise = torch.cat((temp_noise, entry), 2) + else: + temp_noise = entry + + temp_noise_split = None + + gc.collect() + time.sleep(1) + + if Decimal(self.noise_strength).compare(Decimal(1.00)) != 0: + temp_noise = torch.mul(temp_noise, self.noise_strength) + + temp_noise_tuple = torch.split(temp_noise, self.latent_window_size, dim=2) + noise = [item for item in temp_noise_tuple] + temp_noise = None + temp_noise_tuple = None + + if self.verbose_messaging: + if not dummy_noise is None: + print("\nsetup_latent_noise: dummy_noise Shape: {}".format(dummy_noise.shape)) + + i = 0 + for entry in noise: + print("setup_latent_noise: Batch: {} | noise Shape: {}".format(i, entry.shape)) + i += 1 + + print("\n") + + return noise, dummy_noise + + def initialize_comfy_model(self): + comfy_model = HyVideoModel( + HyVideoModelConfig(self.base_dtype), + model_type=comfy.model_base.ModelType.FLOW, + device=self.device, + ) + + patcher = comfy.model_patcher.ModelPatcher(comfy_model, self.device, torch.device("cpu")) + callback = latent_preview.prepare_callback(patcher, self.steps) + + move_model_to_device_with_memory_preservation(self.transformer, target_device=self.device, preserved_memory_gb=self.gpu_memory_preservation) + + return comfy_model, patcher, callback + def get_video_clean_latents(self, video_latents, context_frames, is_plus_one=True): from torch import Tensor latents_tuple = torch.split(video_latents, 1, dim=2) @@ -6460,6 +6510,8 @@ class WarpedFramepackSampler: if context_frames < 3: context_frames = 3 + elif context_frames > self.latent_window_size: + context_frames = self.latent_window_size if context_frames >= video_latents.shape[2]: clean_latent_indices = [] @@ -6542,6 +6594,15 @@ class WarpedFramepackSampler: return history_latents + def setup_dummy_noise(self, width, height): + rnd = torch.Generator("cpu").manual_seed(self.seed) + noise = torch.randn((1, 16, self.dummy_latents, height, width), generator=rnd, device=rnd.device).to(device=rnd.device, dtype=torch.float32) + + if Decimal(self.noise_strength).compare(Decimal(1.00)) != 0: + noise = torch.mul(temp_noise, self.noise_strength) + + return noise + def encoded_to_decoded_length(self, latent_length): if latent_length <= 0: return 0 @@ -6591,6 +6652,10 @@ class WarpedFramepackSampler: next_lowest = preferred_batch_size - 1 next_highest = preferred_batch_size - 1 + if self.verbose_messaging: + print("get_latent_window_size: Next Lowest Initialized To: {}".format(next_lowest)) + print("get_latent_window_size: Next Highest Initialized To: {}".format(next_highest)) + num_frames_final = int(((num_frames - 1) // 4) * 4) + 1 if num_frames != num_frames_final: @@ -6633,6 +6698,51 @@ class WarpedFramepackSampler: print("(6) batch_count set to: {}".format(int((num_frames_final - 1) / next_highest))) return self.decoded_to_encoded_length(next_highest + 1), int((num_frames_final - 1) / next_highest), num_frames_final + print("Unable to find a compatible latent_window_size for number of frames = {} and preferred_batch_size = {}.".format(frame_count, preferred_batch_size)) + print("Recalculating Number Of Frames Based On preferred_batch_size of: {}".format(preferred_batch_size)) + + return self.calculate_new_number_of_frames(preferred_batch_size, (((frame_count - 1) // 4) * 4) + 1, use_batch_size) + + def calculate_new_number_of_frames(self, preferred_batch_size, frame_count, use_batch_size): + working_batch_size = preferred_batch_size - 1 + working_frame_count = frame_count - 1 + + next_lowest = next_highest = working_frame_count + next_lowest_found = False + next_highest_found = False + + while next_lowest > 37: + next_lowest -= 4 + + if int(next_lowest % working_batch_size) == 0: + next_lowest_found = True + break + + if next_lowest_found and (use_batch_size == "next_lowest"): + return self.decoded_to_encoded_length(preferred_batch_size), int(next_lowest // working_batch_size), next_lowest + 1 + + while next_highest < 999997: + next_highest += 4 + + if int(next_highest % working_batch_size) == 0: + next_highest_found = True + break + + if next_highest_found and (use_batch_size == "next_highest"): + return self.decoded_to_encoded_length(preferred_batch_size), int(next_highest // working_batch_size), next_highest + 1 + + if next_lowest_found and next_highest_found: + if (working_frame_count - next_lowest) <= (next_highest - working_frame_count): + return self.decoded_to_encoded_length(preferred_batch_size), int(next_lowest // working_batch_size), next_lowest + 1 + + return self.decoded_to_encoded_length(preferred_batch_size), int(next_highest // working_batch_size), next_highest + 1 + + if next_lowest_found: + return self.decoded_to_encoded_length(preferred_batch_size), int(next_lowest // working_batch_size), next_lowest + 1 + + if next_highest_found: + return self.decoded_to_encoded_length(preferred_batch_size), int(next_highest // working_batch_size), next_highest + 1 + raise ValueError("Unable to find a compatible latent_window_size for number of frames = {} and preferred_batch_size = {}.".format(frame_count, preferred_batch_size)) def clip_vision_encode(self, image, crop="center"): @@ -6643,61 +6753,122 @@ class WarpedFramepackSampler: return output def get_video_latent_embeds(self, video_frames, batch_frame_count, paddings): + if self.verbose_messaging: + print("get_video_latent_embeds: video_frames Shape: {}".format(video_frames.shape)) + print("get_video_latent_embeds: batch_frame_count: {}".format(batch_frame_count)) + print("get_video_latent_embeds: paddings: {}".format(paddings)) + if len(video_frames.shape) < 4: video_frames = video_frames.unsqueeze(0) - image_batches_tuple = torch.split(video_frames, batch_frame_count, dim=0) + first_batch = video_frames[:batch_frame_count, :, :, :] + remaining_batches = video_frames[batch_frame_count:, :, :, :] + + start_image = first_batch[1:2, :, :, :] + end_image = first_batch[(batch_frame_count - 1):, :, :, :] + next_start_image = first_batch[(first_batch.shape[0] - 4):(first_batch.shape[0] - 3), :, :, :] + + start_latent = self.encode_batched(start_image, self.latent_window_size) + start_latent_embedding = self.clip_vision_encode(start_image) + start_image = start_image.to(self.offload_device) + end_latent = self.encode_batched(end_image, self.latent_window_size) + end_latent_embedding = self.clip_vision_encode(end_image) + end_image = end_image.to(self.offload_device) + next_first_latent = end_latent.clone().detach().to(end_latent) + next_first_embedding = deep_copy(end_latent_embedding) + + if self.verbose_messaging: + print("get_video_latent_embeds: first_batch Shape: {}".format(first_batch.shape)) + print("get_video_latent_embeds: remaining_batches Shape: {}".format(remaining_batches.shape)) + print("get_video_latent_embeds: batch 0: start_image Shape: {}".format(start_image.shape)) + print("get_video_latent_embeds: batch 0: start_latent Shape: {}".format(start_latent["samples"].shape)) + print("get_video_latent_embeds: batch 0: end_image Shape: {}".format(end_image.shape)) + print("get_video_latent_embeds: batch 0: end_latent Shape: {}".format(end_latent["samples"].shape)) + print("get_video_latent_embeds: batch 0: next_start_image Shape: {}".format(next_start_image.shape)) + print("get_video_latent_embeds: batch 0: next_first_latent Shape: {}".format(next_first_latent.shape)) + + latent_embeds = {"0": {}} + latent_embeds["0"]["start_latent"] = (start_latent["samples"] * vae_scaling_factor).to(self.offload_device) + latent_embeds["0"]["start_embedding"] = start_latent_embedding + latent_embeds["0"]["end_latent"] = (end_latent["samples"] * vae_scaling_factor).to(self.offload_device) + latent_embeds["0"]["end_embedding"] = end_latent_embedding + + start_image = None + start_latent = None + start_latent_embedding = None + end_image = None + end_latent = None + end_latent_embedding = None + next_start_four = None + next_start_image = None + + image_batches_tuple = torch.split(remaining_batches, batch_frame_count - 1, dim=0) image_batches_split = [item for item in image_batches_tuple] image_batches_tuple = None - latent_embeds = {} + if self.verbose_messaging: + i = 0 + for entry in image_batches_split: + print("get_video_latent_embeds: batch: {} | split Shape: {}".format(i + 1, image_batches_split[i].shape)) + i += 1 print("get_video_latent_embeds Batch: Encoding Start/End Images...") - start_latent = None - start_latent_embedding = None + batch_number = 1 + for batch in image_batches_split: + batch_key = "{}".format(batch_number) - i = 0 - for batch_number in paddings: - batch = image_batches_split[batch_number] + if self.verbose_messaging: + print("get_video_latent_embeds: Processing Batch: {}".format(batch_number)) - batch_image_batches_tuple = torch.split(batch, 1, dim=0) - batch_image_batches_split = [item for item in batch_image_batches_tuple] - batch_image_batches_tuple = None + latent_embeds[batch_key] = {"start_latent": None, "end_latent": None, "start_embedding": None, "end_embedding": None} - latent_embeds["{}".format(batch_number)] = {"start_latent": None, "end_latent": None, "start_embedding": None, "end_embedding": None} + start_latent = next_first_latent + start_latent_embedding = next_first_embedding - if batch.shape[0] == batch_frame_count: - start_index = batch.shape[0] - 1 + end_image = batch[(batch.shape[0] - 1):, :, :, :] + end_image = end_image.to(self.device) + end_latent = self.encode_batched(end_image, self.latent_window_size) + end_latent_embedding = self.clip_vision_encode(end_image) + end_image = end_image.to(self.device) - start_image = batch_image_batches_split[0].clone().detach().to(self.device) - start_latent = self.encode_batched(start_image, self.latent_window_size) - start_latent_embedding = self.clip_vision_encode(start_image) + latent_embeds[batch_key]["start_latent"] = (start_latent["samples"] * vae_scaling_factor).to(self.offload_device) + latent_embeds[batch_key]["start_embedding"] = start_latent_embedding + latent_embeds[batch_key]["end_latent"] = (end_latent["samples"] * vae_scaling_factor).to(self.offload_device) + latent_embeds[batch_key]["end_embedding"] = end_latent_embedding - end_image = batch_image_batches_split[len(batch_image_batches_split) - 1].clone().detach().to(self.device) - end_latent = self.encode_batched(end_image, self.latent_window_size) - end_latent_embedding = self.clip_vision_encode(end_image) + if batch_number < max(paddings): + next_start_image = batch[(batch.shape[0] - 1):, :, :, :] + next_first_latent = self.encode_batched(next_start_image, self.latent_window_size) + next_first_embedding = copy.deepcopy(end_latent_embedding) + next_start_image = None + else: + next_first_embedding = None + next_start_image = None + next_first_latent = None - latent_embeds["{}".format(batch_number)]["start_latent"] = start_latent["samples"] * vae_scaling_factor - latent_embeds["{}".format(batch_number)]["start_embedding"] = start_latent_embedding - latent_embeds["{}".format(batch_number)]["end_latent"] = end_latent["samples"] * vae_scaling_factor - latent_embeds["{}".format(batch_number)]["end_embedding"] = end_latent_embedding + start_image = None + start_latent = None + start_latent_embedding = None + end_image = None + end_latent = None + end_latent_embedding = None + batch_image_batches_split = None - start_image = None - start_latent = None - start_latent_embedding = None - end_image = None - end_latent = None - end_latent_embedding = None - batch_image_batches_split = None - - i += 1 + batch_number += 1 print("get_video_latent_embeds Batch: Encoding Start/End Images...Done") - mm.soft_empty_cache() - gc.collect() - time.sleep(0.2) + if self.verbose_messaging: + print("get_video_latent_embeds: latent_embeds length: {}".format(len(latent_embeds))) + + for batch_key in latent_embeds.keys(): + print("get_video_latent_embeds: Batch {}: start_latent Shape: {}".format(batch_key, latent_embeds[batch_key]["start_latent"].shape)) + print("get_video_latent_embeds: Batch {}: end_latent Shape: {}".format(batch_key, latent_embeds[batch_key]["end_latent"].shape)) + print("get_video_latent_embeds: Batch {}: start_embedding Shape: {}".format(batch_key, latent_embeds[batch_key]["start_embedding"]["last_hidden_state"].shape)) + print("get_video_latent_embeds: Batch {}: end_embedding Shape: {}".format(batch_key, latent_embeds[batch_key]["end_embedding"]["last_hidden_state"].shape)) + + self.cleanup(unload_models=False, cleanup_models=False, cleanup_cuda=True) return latent_embeds @@ -6718,14 +6889,13 @@ class WarpedFramepackSampler: decoded_data.unsqueeze(0) return decoded_data - def decode_batches(self, latent_batches, skip_frames, is_v2v=False): + + def decode_batches(self, latent_batches, skip_frames): if (latent_batches is None) or (len(latent_batches) < 1): print("decode_batches: Warning...nothing to decode.") return None - mm.unload_all_models() - gc.collect() - time.sleep(1) + self.cleanup(unload_models=True, cleanup_models=False, cleanup_cuda=False) resulting_images = None @@ -6742,12 +6912,8 @@ class WarpedFramepackSampler: if len(temp_decoded.shape) < 4: temp_decoded = temp_decoded.unsqueeze(0) - if is_v2v: - image_batches_tuple = torch.split(temp_decoded, temp_decoded.shape[0] - 3, dim=0) - image_batches_split = [item for item in image_batches_tuple] - temp_decoded = image_batches_split[0] - image_batches_tuple = None - image_batches_split = None + # if i > 0: + # temp_decoded = temp_decoded[3:, :, :, :] if not resulting_images is None: resulting_images = torch.cat((resulting_images, temp_decoded), 0) @@ -6756,9 +6922,7 @@ class WarpedFramepackSampler: entry.to(device=self.offload_device) - mm.soft_empty_cache() - gc.collect() - time.sleep(0.2) + self.cleanup(unload_models=False, cleanup_models=False, cleanup_cuda=True) i += 1 else: @@ -6776,84 +6940,17 @@ class WarpedFramepackSampler: if len(temp_decoded.shape) < 4: temp_decoded = temp_decoded.unsqueeze(0) - if is_v2v: - image_batches_tuple = torch.split(temp_decoded, temp_decoded.shape[0] - 3, dim=0) - image_batches_split = [item for item in image_batches_tuple] - temp_decoded = image_batches_split[0] - image_batches_tuple = None - image_batches_split = None - temp_decoded_batches.append(temp_decoded) entry.to(device=self.offload_device) - mm.soft_empty_cache() - gc.collect() - time.sleep(0.2) + self.cleanup(unload_models=False, cleanup_models=False, cleanup_cuda=True) i += 1 - blend_value = 1.0 / self.blend_frames - i = 0 - while i < (len(temp_decoded_batches) - 1): - alpha_blend_val = blend_value - blend_count = self.blend_frames - - image_batches_tuple = torch.split(temp_decoded_batches[i], 1, dim=0) - image_batches_split = [item for item in image_batches_tuple] - image1 = image_batches_split[len(image_batches_split) - 1] - image_batches_tuple = None - image_batches_split = None - - image_batches_tuple = torch.split(temp_decoded_batches[i + 1], 1, dim=0) - image_batches_split = [item for item in image_batches_tuple] - image2 = image_batches_split[0] - image_batches_tuple = None - image_batches_split = None - - image1 = tensor2pilSwap(image1)[0] - image2 = tensor2pilSwap(image2)[0] - - blend_latents = None - - while blend_count > 0: - blended_image = Image.blend(image1, image2, alpha_blend_val) - temp_latent = pil2tensorSwap(blended_image) - blended_image = None - - if len(temp_latent.shape) < 4: - temp_latent = temp_latent.unsqueeze(0) - - if not blend_latents is None: - blend_latents = torch.cat((blend_latents, temp_latent), 0) - else: - blend_latents = temp_latent - - alpha_blend_val += blend_value - blend_count -= 1 - - temp_decoded_batches[i] = torch.cat((temp_decoded_batches[i], blend_latents), 0) - blend_latents = None - - mm.soft_empty_cache() - gc.collect() - time.sleep(0.2) - - i += 1 - - for entry in temp_decoded_batches: - if not resulting_images is None: - resulting_images = torch.cat((resulting_images, entry), 0) - else: - resulting_images = entry - - entry.to(device=self.offload_device) - + resulting_images = self.assemble_final_result(image_batches) temp_decoded_batches = None - mm.soft_empty_cache() - gc.collect() - time.sleep(1) - + self.cleanup(unload_models=False, cleanup_models=False, cleanup_cuda=True) print("decode_batches: Full decoded images count: {}".format(resulting_images.shape[0])) @@ -6881,6 +6978,110 @@ class WarpedFramepackSampler: return resulting_images + def assemble_final_result(self, image_batches): + if self.blend_frames < 1: + resulting_images = None + for entry in image_batches: + if not resulting_images is None: + resulting_images = torch.cat((resulting_images, entry), 0) + else: + resulting_images = entry + + entry.to(device=self.offload_device) + entry = None + else: + blend_value = 1.0 / self.blend_frames + i = 0 + while i < (len(image_batches) - 1): + alpha_blend_val = blend_value + blend_count = self.blend_frames + + image_batches_tuple = torch.split(image_batches[i], 1, dim=0) + image_batches_split = [item for item in image_batches_tuple] + image1 = image_batches_split[len(image_batches_split) - 1] + # image1 = image_batches_split[len(image_batches_split) - 4] + image_batches_tuple = None + image_batches_split = None + + image_batches_tuple = torch.split(image_batches[i + 1], 1, dim=0) + image_batches_split = [item for item in image_batches_tuple] + image2 = image_batches_split[0] + image_batches_tuple = None + image_batches_split = None + + image1 = tensor2pilSwap(image1)[0] + image2 = tensor2pilSwap(image2)[0] + + blend_latents = None + + while blend_count > 0: + blended_image = Image.blend(image1, image2, alpha_blend_val) + temp_latent = pil2tensorSwap(blended_image) + blended_image = None + + if len(temp_latent.shape) < 4: + temp_latent = temp_latent.unsqueeze(0) + + if not blend_latents is None: + blend_latents = torch.cat((blend_latents, temp_latent), 0) + else: + blend_latents = temp_latent + + alpha_blend_val += blend_value + blend_count -= 1 + + image_batches_tuple = torch.split(image_batches[i], image_batches[i].shape[0] - 3, dim=0) + image_batches_split = [item for item in image_batches_tuple] + image_batches_tuple = None + + image_batches[i] = torch.cat((image_batches_split[0], blend_latents), 0) + blend_latents = None + image_batches_split = None + + self.cleanup(unload_models=False, cleanup_models=False, cleanup_cuda=True) + + i += 1 + + resulting_images = None + for entry in image_batches: + if not resulting_images is None: + resulting_images = torch.cat((resulting_images, entry), 0) + else: + resulting_images = entry + + entry.to(device=self.offload_device) + + image_batches = None + + self.cleanup(unload_models=False, cleanup_models=False, cleanup_cuda=True) + + + print("assemble_final_result: Full decoded images count: {}".format(resulting_images.shape[0])) + + if self.skip_frames < 1: + return resulting_images + + skipped_frames = 1 + + image_batches_tuple = torch.split(resulting_images, 1, dim=0) + image_batches_split = [item for item in image_batches_tuple] + + resulting_images = None + + for image in image_batches_split: + if skipped_frames <= self.skip_frames: + skipped_frames += 1 + continue + + if not resulting_images is None: + resulting_images = torch.cat((resulting_images, image), 0) + else: + resulting_images = image + + print("assemble_final_result: Final decoded images count: {}".format(resulting_images.shape[0])) + + return resulting_images + def encode_batched(self, image, per_batch=16): from nodes import VAEEncode from comfy.utils import ProgressBar @@ -6897,61 +7098,111 @@ class WarpedFramepackSampler: return {"samples": torch.cat(t, dim=0)} + def cleanup(self, unload_models=False, cleanup_models=False, cleanup_cuda=False): + if unload_models: + mm.unload_all_models() + + if cleanup_models: + mm.cleanup_models() + + if cleanup_cuda: + mm.soft_empty_cache() + + gc.collect() + time.sleep(1) + + return + def video_encode(self, video_frames, batch_size): if len(video_frames.shape) < 4: video_frames = video_frames.unsqueeze(0) - image_batches_tuple = torch.split(video_frames, batch_size - 1, dim=0) + print(f"video_encode: Encoding input video frames in batch size {batch_size} (reduce preferred_batch_size if memory issues here or if forcing video resolution)") + + if self.verbose_messaging: + print("video_encode: video_frames Shape: {}".format(video_frames.shape)) + print("video_encode: batch_size: {}".format(batch_size)) + + first_batch = video_frames[:batch_size, :, :, :] + remaining_batches = video_frames[batch_size:, :, :, :] + # next_start_image = first_batch[(first_batch.shape[0] - 4):(first_batch.shape[0] - 3), :, :, :] + next_start_four = first_batch[(first_batch.shape[0] - 4):, :, :, :] + + first_batch_latents = partial_encode_tiled(self.vae, first_batch, unload_after=False) + + if self.verbose_messaging: + print("video_encode: first_batch Shape: {}".format(first_batch.shape)) + print("video_encode: remaining_batches Shape: {}".format(remaining_batches.shape)) + # print("video_encode: next_start_image Shape: {}".format(next_start_image.shape)) + print("video_encode: next_start_four Shape: {}".format(next_start_four.shape)) + + if len(first_batch_latents.shape) < 5: + first_batch_latents = first_batch_latents.unsqueeze(0) + + first_batch_latents = first_batch_latents * vae_scaling_factor + final_latent_batches = [first_batch_latents.to(self.offload_device)] + + image_batches_tuple = torch.split(remaining_batches, batch_size - 1, dim=0) image_batches_split = [item for item in image_batches_tuple] final_image_batches = [] last_frame = None - for entry in image_batches_split: - if entry.shape[0] == (batch_size - 1): - final_image_batches.append(entry.clone().detach()) - else: - if last_frame is None: - last_frame = entry - - print(f"video_encode: Encoding input video frames in batch size {batch_size} (reduce preferred_batch_size if memory issues here or if forcing video resolution)") latents = None - final_latent_batches = [] batch_encodings = [] - i = 1 with torch.no_grad(): - for entry in final_image_batches: - entry.to(dtype=torch.float32, device=self.device) + i = 0 + for entry in image_batches_split: + entry = entry.to(dtype=torch.float32, device=self.device) + entry = torch.cat((next_start_four, entry), 0) + # next_start_image = entry[(entry.shape[0] - 1):, :, :, :] + next_start_four = entry[(entry.shape[0] - 4):, :, :, :] - if (i < len(final_image_batches)): - next_start_image = final_image_batches[i][1:2, :, :, :] - - entry = torch.cat((entry, next_start_image.to(entry)), 0) + if i < (len(image_batches_split) - 1): batch_latent = partial_encode_tiled(self.vae, entry, unload_after=False) else: - entry = torch.cat((entry, last_frame.to(entry)), 0) batch_latent = partial_encode_tiled(self.vae, entry, unload_after=True) - i += 1 - if len(batch_latent.shape) < 5: batch_latent = batch_latent.unsqueeze(0) batch_latent = batch_latent * vae_scaling_factor + batch_latent = batch_latent.to(self.offload_device) final_latent_batches.append(batch_latent.clone().detach()) - entry.to(device=self.offload_device) + entry = entry.to(device=self.offload_device) + entry = None + batch_latent = None print(f"Encoding input video frames in batch size {batch_size} Done.") + if self.verbose_messaging: + i = 0 + for entry in final_latent_batches: + print("video_encode: Batch: {} | batch Shape: {}".format(i, entry.shape)) + i += 1 + return final_latent_batches, batch_encodings +def cleanup(device=None, unload_models=False, cleanup_cuda=False): + if unload_models: + mm.free_memory(1e30, device) + + if cleanup_cuda: + torch.cuda.empty_cache() + torch.cuda.ipc_collect() + + gc.collect() + time.sleep(1) + + return + class WarpedFramepackLoraSelectBatch: @classmethod def INPUT_TYPES(s): return { "required": { - "lora": ("STRING", {"default": ""}), + "lora": ("STRING", {"default": "", "forceInput": True}), "strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.0001, "tooltip": "LORA strength, set to 0.0 to unmerge the LORA"}), "fuse_lora": ("BOOLEAN", {"default": False, "tooltip": "Fuse the LORA model with the base model. This is recommended for better performance."}), }, @@ -6975,10 +7226,12 @@ class WarpedFramepackLoraSelectBatch: "name": lora.split(".")[0], "fuse_lora": fuse_lora, } + if prev_lora is not None: loras_list.extend(prev_lora) loras_list.append(lora) + return (loras_list,) def warped_load_torch_file(ckpt, return_metadata=False): @@ -7030,20 +7283,19 @@ class WarpedDualEncoder(ComfyNodeABC): RETURN_TYPES = (IO.CONDITIONING, IO.CONDITIONING, ) RETURN_NAMES = ("pos_conditioning", "neg_conditioning", ) FUNCTION = "process" - - CATEGORY = "Warped/Conditioning" - + CATEGORY = "Warped/General/Conditioning" DESCRIPTION = "Encodes both positive and negative prompts." def process(self, clip, positive_text="", negative_text=""): - if clip.patcher.model.device != clip.patcher.load_device: - clip.patcher.model.to(device=clip.patcher.load_device) + print("WarpedDualEncoder: Loading clip model to device: {}".format(clip.patcher.load_device)) + clip.patcher.model.to(device=clip.patcher.load_device) print("WarpedDualEncoder: Encoding Prompts...") positive_conditioning = self.encode(clip, positive_text) negative_conditioning = self.encode(clip, negative_text) print("WarpedDualEncoder: Encoding Prompts...Done.") + print("WarpedDualEncoder: Unloading clip model to device: {}".format(get_offload_device())) clip.patcher.model.to(device=get_offload_device()) if torch.cuda.is_available(): @@ -7077,7 +7329,7 @@ class WarpedDualCLIPLoader: RETURN_NAMES = ("clip", ) FUNCTION = "load_clip" - CATEGORY = "Warped/Loaders" + CATEGORY = "Warped/General/Loaders" DESCRIPTION = "[Recipes]\n\nsdxl: clip-l, clip-g\nsd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\nflux: clip-l, t5" @@ -7158,7 +7410,7 @@ class WarpedLoadFramePackModel: "base_precision": (["fp32", "bf16", "fp16"], {"default": "bf16"}), "quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2'], {"default": 'disabled', "tooltip": "optional quantization method"}), - "load_device": (["main_device", "offload_device"], {"default": "offload_device", "tooltip": "Initialize the model on the main device or offload device"}), + "load_device": (get_available_devices(), {"default": "cpu", "tooltip": "set load device."}), }, "optional": { "attention_mode": ([ @@ -7189,12 +7441,17 @@ class WarpedLoadFramePackModel: base_dtype = {"fp8_e4m3fn": torch.float8_e4m3fn, "fp8_e4m3fn_fast": torch.float8_e4m3fn, "bf16": torch.bfloat16, "fp16": torch.float16, "fp16_fast": torch.float16, "fp32": torch.float32}[base_precision] - device = mm.get_torch_device() offload_device = get_offload_device() - if load_device == "main_device": - transformer_load_device = device + + if load_device == "cuda": + device = mm.get_torch_device() + elif load_device == "cpu": + device = offload_device else: - transformer_load_device = offload_device + temp_strings = load_device.split(':') + device = torch.device(int(temp_strings[len(temp_strings) - 1])) + + transformer_load_device = offload_device model_path = folder_paths.get_full_path_or_raise("diffusion_models", model) model_config_path = os.path.join(script_directory, "transformer_config.json") @@ -7252,6 +7509,11 @@ class WarpedLoadFramePackModel: for l in lora: fuse = True if l["fuse_lora"] else False + blocktypes = None + + if "blocks" in l.keys(): + blocktypes = l["blocks"] + print("Reading LORA: {}".format(l["path"])) with open(l["path"], "rb") as file: lora_temp = file.read() @@ -7261,6 +7523,13 @@ class WarpedLoadFramePackModel: print("Loading Lora: {}...Done".format(lora_number)) lora_number += 1 + # Convert Lora To diffusion_model format + lora_sd = convert_lora(lora_sd, convert_to="diffusion_model") + + # If blocks value exist in lora metadata, then select block types + if not blocktypes is None: + lora_sd = filter_lora_keys(lora_sd, blocktypes) + if "lora_unet_single_transformer_blocks_0_attn_to_k.lora_up.weight" in lora_sd: from .utils import convert_to_diffusers lora_sd = convert_to_diffusers("lora_unet_", lora_sd) @@ -7285,6 +7554,7 @@ class WarpedLoadFramePackModel: del lora_sd mm.soft_empty_cache() + if adapter_list: transformer.set_adapters(adapter_list, weights=adapter_weights) if fuse: @@ -7305,7 +7575,6 @@ class WarpedLoadFramePackModel: from .fp8_optimization import convert_fp8_linear convert_fp8_linear(transformer, base_dtype, params_to_keep=params_to_keep) - DynamicSwapInstaller.install_model(transformer, device=device) if compile_args is not None: @@ -7316,8 +7585,6 @@ class WarpedLoadFramePackModel: for i, block in enumerate(transformer.transformer_blocks): transformer.transformer_blocks[i] = torch.compile(block, fullgraph=compile_args["fullgraph"], dynamic=compile_args["dynamic"], backend=compile_args["backend"], mode=compile_args["mode"]) - #transformer = torch.compile(transformer, fullgraph=compile_args["fullgraph"], dynamic=compile_args["dynamic"], backend=compile_args["backend"], mode=compile_args["mode"]) - pipe = { "transformer": transformer.eval(), "dtype": base_dtype, @@ -7349,9 +7616,9 @@ class WarpedCLIPVisionLoader: return {"required": { "clip_name": (folder_paths.get_filename_list("clip_vision"), ), }} RETURN_TYPES = ("CLIP_VISION",) + RETURN_NAMES = ("clip_vision",) FUNCTION = "load_clip" - - CATEGORY = "Warped/Loaders" + CATEGORY = "Warped/General/Loaders" def load_clip(self, clip_name): clip_path = folder_paths.get_full_path_or_raise("clip_vision", clip_name) @@ -7450,10 +7717,11 @@ class WarpedVAELoader: @classmethod def INPUT_TYPES(s): return {"required": { "vae_name": (s.vae_list(), )}} - RETURN_TYPES = ("VAE",) + RETURN_TYPES = ("VAE", "DICT", ) + RETURN_NAMES = ("vae", "vae_state_dict", ) FUNCTION = "load_vae" - CATEGORY = "Warped/Loaders" + CATEGORY = "Warped/General/Loaders" #TODO: scale factor? def load_vae(self, vae_name): @@ -7475,4 +7743,200 @@ class WarpedVAELoader: time.sleep(1) vae.throw_exception_if_invalid() - return (vae,) + return (vae, sd, ) + +class WarpedNumericalConversion: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "int_value": ("INT", {"default": None, "forceInput": True}), + "float_value": ("FLOAT", {"default": None, "forceInput": True}), + "number_value": ("NUMBER", {"default": None, "forceInput": True}), + } + } + + RETURN_TYPES = ("INT","FLOAT", "NUMBER", "STRING", ) + RETURN_NAMES = ("int","float", "number", "string", ) + OUTPUT_NODE = True + FUNCTION = "int_to_number" + CATEGORY = "Warped/General/Utils" + + def int_to_number(self, int_value=None, float_value=None, number_value=None): + value = None + + if (int_value == None) and (float_value == None) and (number == None): + raise ValueError("WarpedNumericalConversion: All inputs are None. Nothing to convert.") + + i = 0 + + if not int_value is None: + i += 1 + value = int_value + + if not float_value is None: + i += 1 + value = float_value + + if not number_value is None: + i += 1 + value = number_value + + if i > 1: + raise ValueError("WarpedNumericalConversion: More than one type of value inputs simultaneously is not supported.") + + if not value is None: + return {"ui": {"string": ["{}".format(value),]}, "result": (int(value), float(value), value, "{}".format(value),), } + + return {"ui": {"string": ["{}".format(0),]}, "result": (int(0), float(0), value, "0",), } + +class WarpedLoraReSave: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "lora_name": (folder_paths.get_filename_list("loras"),), + "save_path": ("STRING", {"default": get_default_gen_output_path()}), + "model_name": ("STRING", {"default": ""}), + }, + } + + RETURN_TYPES = () + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + FUNCTION = "load_lora" + CATEGORY = "Warped/General/Lora" + DESCRIPTION = "Re-saves LORA with new metadata." + + def get_metadata(self, lora_path, model_name): + model_config_path = os.path.join(script_directory, "your_metadata.json") + model_config_path = os.path.abspath(model_config_path) + + print("metadata config file path: {}".format(model_config_path)) + + try: + with open(model_config_path, "r") as f: + metadata = json.load(f) + + if len(model_name) > 0: + metadata["model_name"] = model_name + + return metadata + except Exception as e: + print("Exception attempting to read config file: {}".format(model_config_path)) + raise e + + return {"metadata": "No Metadata" } + + def load_lora(self, lora_name: str, save_path: str, model_name: str) -> Tuple[Dict[str, torch.Tensor], Dict[str, torch.Tensor]]: + if not lora_name: + return {"ui": {"tags": ["Nothing here to Re-save"]}} + + # Get the full path to the LoRA file + lora_path = folder_paths.get_full_path("loras", lora_name) + if not os.path.exists(lora_path): + raise ValueError(f"LoRA file not found: {lora_path}") + + lora_weights = comfy.utils.load_torch_file(lora_path) + + metadata = self.get_metadata(lora_path, model_name) + + temp_strings = save_path.split('\\') + del temp_strings[len(temp_strings) - 1] + + print("Save Path: {}".format(save_path)) + + save_folder = "" + for temp_string in temp_strings: + if len(save_folder) > 0: + save_folder = "{}\\{}".format(save_folder, temp_string) + else: + save_folder = temp_string + + print("Save_folder: {}".format(save_folder)) + os.makedirs(save_folder, exist_ok = True) + + utils.save_torch_file(lora_weights, save_path, metadata=metadata) + + save_message = "Weights Saved To: {}".format(save_path) + + return {"ui": {"tags": [save_message]}} + +class WarpedReverseImageBatch: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE", {"tooltip": "Image batch to reverse."}), + } + } + RETURN_TYPES = ("IMAGE", ) + RETURN_NAMES = ("images", ) + FUNCTION = "reverse_images" + CATEGORY = "Warped/General/Image" + DESCRIPTION = "Reverses the order of the batch of images input into the node." + + def reverse_images(self, images=None): + if images is None: + raise ValueError("images input cannot be None.") + + images_tuple = torch.split(images, 1, dim=0) + images_split = [item for item in images_tuple] + + images_split.reverse() + + resulting_images = None + for temp_image in images_split: + if not resulting_images is None: + resulting_images = torch.cat((resulting_images, temp_image), 0) + else: + resulting_images = temp_image + + return (resulting_images, ) + +class WarpedCheckpointLoader: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"), {"tooltip": "The name of the checkpoint (model) to load."}), + } + } + RETURN_TYPES = ("MODEL", "CLIP", "VAE", ) + RETURN_NAMES = ("model", "clip", "vae", ) + OUTPUT_TOOLTIPS = ("The model used for denoising latents.", + "The CLIP model used for encoding text prompts.", + "The VAE model used for encoding and decoding images to and from latent space.") + FUNCTION = "load_checkpoint" + CATEGORY = "Warped/General/Loaders" + DESCRIPTION = "Loads a diffusion model checkpoint, diffusion models are used to denoise latents." + + def load_checkpoint(self, ckpt_name): + ckpt_path = folder_paths.get_full_path_or_raise("checkpoints", ckpt_name) + out = self.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) + return out[:3] + + def load_checkpoint_guess_config(self, ckpt_path, output_vae=True, output_clip=True, embedding_directory=None): + print("Reading: {}".format(ckpt_path)) + with open(ckpt_path, "rb") as file: + checkpoint_temp = file.read() + + print("Loading Checkpoint...") + ckpt = warped_load_torch_file(checkpoint_temp) + print("Loading Checkpoint...Done") + checkpoint_temp = None + + gc.collect() + time.sleep(1) + + out = comfy.sd.load_state_dict_guess_config(ckpt, output_vae, output_clip, embedding_directory) + if out is None: + raise RuntimeError("ERROR: Could not detect model type of: {}".format(ckpt_path)) + + return out diff --git a/pyproject.toml b/pyproject.toml index 17b71f2..83dc1fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ComfyUI-WarpedToolset" description = "A toolset for Hunyuan Video (mainly), with some additional nodes applicable to Framepack Video and WAN Video." -version = "1.6.3" +version = "1.7.0" license = { file = "LICENSE" } [project.urls] diff --git a/requirements.txt b/requirements.txt index 36b4631..0295db8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ accelerate safetensors peft +einops diff --git a/your_metadata.json b/your_metadata.json new file mode 100644 index 0000000..5756da8 --- /dev/null +++ b/your_metadata.json @@ -0,0 +1,4 @@ +{ + "creator": "Warped Animation", + "website": "https://subscribestar.adult/warped-animation" +}