-
Notifications
You must be signed in to change notification settings - Fork 7.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Node Expansion, While Loops, Components, and Lazy Evaluation #931
Conversation
This PR inverts the execution model -- from recursively calling nodes to using a topological sort of the nodes. This change allows for modification of the node graph during execution. This allows for two major advantages: 1. The implementation of lazy evaluation in nodes. For example, if a "Mix Images" node has a mix factor of exactly 0.0, the second image input doesn't even need to be evaluated (and visa-versa if the mix factor is 1.0). 2. Dynamic expansion of nodes. This allows for the creation of dynamic "node groups". Specifically, custom nodes can return subgraphs that replace the original node in the graph. This is an *incredibly* powerful concept. Using this functionality, it was easy to implement: a. Components (a.k.a. node groups) b. Flow control (i.e. while loops) via tail recursion c. All-in-one nodes that replicate the WebUI functionality d. and more All of those were able to be implemented entirely via custom nodes without hooking or replacing any core functionality. Within this PR, I've included all of these proof-of-concepts within a custom node pack. In reality, I would expect some number of them to be merged into the core node set (with the rest left to be implemented by custom nodes). I made very few changes to the front-end, so there are probably some easy UX wins for someone who is more willing to wade into .js land. The user experience is a lot better than I expected though -- progress shows correctly in the UI over the nodes that are being expanded.
Here's an example workflow and screenshot of lazy evaluation. Nodes can skip evaluating entire subtrees if they're unnecessary. |
This looks pretty nice. I'll have to get a portable going to test the PR tomorrow. |
Does anyone have a suggestion of a node group that makes heavy use of list inputs and/or outputs? That's one place where I think things work, but haven't actually tested much beyond WAS' image-to-seed/noise nodes. My two concerns are:
|
When creating an unreachable point through disconnection like this, the execution of the main will execute all the executable parts and produce error messages while executing the unreachable parts. However, in the current PR, there is an issue where it executes the unreachable parts without completing the executable parts, resulting in an error. In #731, when encountering an unreachable part, it adopts a strategy of gracefully interrupting only the corresponding execution path. It would be advisable to consider such an approach. |
@ltdrdata I'm not sure I understand. I ran your workflow with both that switch disconnected and connected (albeit with the default 1.5 checkpoint since I don't have SDXL yet) and saw the result I expected each time. With the switch disconnected, the parts of the graph that could run did (properly displaying the outputs) and the parts that couldn't displayed errors about why they couldn't run. When the switch was connected, the entire graph ran successfully. When I disconnected the switch again, I saw the same result as in the first image. Am I misunderstanding your concern? Are you looking to explicitly make failures silent when they're due to missing required inputs rather than displaying the red outlines? |
I conducted the experiment with the following steps:
Symptoms: If it is not reproducible then it means there is random execution order. |
I've put together a checklist for execution model migration for Workflow Component.
Once I have tested the individual sub-functionalities and confirmed that there are no issues, I can proceed with the migration task. |
Previously, nodes that were connected to output nodes only via optional inputs were never validated, but were still run. This would cause a Python error and stop execution wherever we happen to be in the execution list. This bug exists on master, but may be more noticeable in this branch because execution order is non-deterministic. Other minor change this commit introduces: `raw_link` can be specified as an option on an input to receive the raw link (in the standard form of [node_id, output_index]) rather than a resolved value.
Note for future me (or others): Reference code for the nodes @ltdrdata is mentioning are located here: |
Note that For loops still require WAS-ns. They just expand to using the integer operation node internally. Also adds some nodes for accumulation operations.
Since everybody seems to want to implement For loops anyway, I've added some syntactic sugar for it. Note that they still require WAS-ns -- they just expand to the nodes in the example above (which also helped me test that loops work when expanded themselves). Here's an example of what that looks like. This example also includes the new accumulation nodes: To demo a more complex operation that uses accumulations, the following performs pairwise differences on the above images: Here's the workflow for those: |
In my opinion, there is a need for basic condition nodes to be built-in to support loops. Although the loop functionality itself seems to work well, it would be great to have a more concise way to elaborate on it. I think it would be necessary to conduct some research while playing spaghetti play once. |
Yeah, I totally agree. I just wanted to be careful about how many new nodes I introduce, so I figured it made since to add the minimal number to get things functional and then add more based on feedback once people (including myself) were actually using the thing. |
This is more easier reproducible issue. |
I'm trying out the custom components workflow you described; however, I'm getting the following error when I try to run my graph.
(I'm including the graph that triggers this error) |
Honestly, I'm still a little concerned here. There's nothing stopping a custom node from having a data type of ["str",int]. I've improved recognition to at least prevent the detection of other types, but we may still want a more systemic fix (e.g. wrapping literals within a class when using them as inputs to nodes in subgraphs).
Thanks for the reproduction case @PGadoury. This specific error was caused by the concern I mentioned in one of my other comments -- most of the core code differentiates links from literals by assuming that all lists are links. For now I've committed a fix that limits recognition to lists of the form |
Rather than displaying live feedback on the "End For" node, the feedback will be displayed in the UI on the original node.
Could I chime in with a thought? Perhaps we could mimic what they do with blueprints in Unreal Engine for the control structures? https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/Blueprints/UserGuide/FlowControl/ |
I just need to take some time to properly look at it and test it. Knowing that other people are using it without issues is very useful because it means I need to do less testing. |
Yes, I've been using this fork for my project and it's been working great! I've only come across one issue, which is more of an annoyance than a bug: It would be nice if the node parameter validation was also lazy. For example, if a checkpoint loader node won't be used with the current workflow settings, I shouldn't be required to select a checkpoint in order for the workflow to validate. It can be a bit annoying to put in several "required" parameters when they really won't be needed. |
I am also happy to continue making changes/fixes in it if someone identifies something that needs to be changed. It's been discussed a couple times on the matrix channel, but I think it would be appropriate to merge the execution model changes (i.e. everything not in the custom_nodes folder in this PR) even if we don't immediately want to add nodes for loops/etc. in the base ComfyUI until the front-end experience is improved (specifically related to Once the execution model is updated, the rest of the example use-cases in this PR can be added piecemeal as @comfyanonymous feels the UX for each one reaches an appropriate level. In the meantime, power-users would continue being able to use those nodes as custom nodes without working off of a totally separate fork. |
This is one power-user who could desperately benefit from this. I loved the enhancements so I had to give these a try. My merge worked at first, but I decided I wanted to update ComfyUI and ended up breaking everything :/. Just had to reset and try to do it all over again. It was a bit of a pain to get things working in the first place as I was dealing with a 3rd handful of files (had to fiddle with RG3's custom node to get it up-and-running). Guill, any good resources I could read regarding how to do this sorta thing? And/or is there something I'm missing as far as these files reconciling with the ComfyUI updates? Just seems to me that I have no way of knowing if when I patch in #931's version of files, that any unrelated tweaks/enhancements made to the master branch will (or won't) stick around after I patch the repo. I'd like to keep up with the master branch! |
I really like the inverted control from an architectural point of view. It's conceptually good and will open quite a few possibilities. In order to make progress, I concur that the execution model changes should be factored out so that it can be tested and reviewed without all the "noise" from the other additions. That also makes it easier to test things and not be distracted by shiny objects. I know it's boring to have a PR where, ideally, nobody notices any change, but for the sake of stability this is a good thing. |
I second the idea of pulling in the execution model changes now. The current version of ComfyUI has drifted a lot from what this branch is based on, and as someone who's tried merging them together, it's very hard to merge the two together; it'll only get worse with time. Pulling in the execution model changes should at least make sure the version drift isn't a problem. (Plus I know at least one custom node developer has cited this pull request as solving a lot of problems for his nodes.) That being said, I've noticed a minor UX problem: the Save/Load Component buttons take up a lot of screen real estate, and frankly, I don't think they need to be there. I'm pretty sure you can just use the API format to save components instead, since the API format is designed to make it easy to parse the actual workflow structure. You'd need to expose the Save (API Format) button outside of developer mode, but I don't think that's a huge issue, and you'd remove the need for a separate Load Component button entirely, since the default Load button can load in API-formatted workflows just fine. |
I've found another route that can partially implement these features: ComfyScript: A Python front end for ComfyUI. The basic idea is that one can generate workflows dynamically in Python. This way, control flows that don't rely on intermediate results can be implemented naturally. And for control flows that rely on intermediate results, it's still feasible to mimic them by expanding the workflow dynamically and run the workflow multiple times. This is not user friendly for most users as it requires some knowledge of Python, but it could be a usable option for power users. |
You can ignore the Save/Load Component button. It's just a PoC. It will be removed when merge. |
There seems to be a lot of interest in this so if you split out the execution part I will merge it assuming it doesn't break anything. |
Very much looking forward to this PR being merged into master, @guill can we continue to discuss plans on how to merge this PR in? |
Awesome! Going through a major launch at work-work this week, but this weekend I should have time to merge main back into this branch and get the core pieces separated out into a separate branch 👍 |
Finally!!! |
Also @guill I think the current mechanism of introducing new key Maybe one solution is to pass optional current node instance id for |
@Trung0246 Yeah, there are quite a few issues with dynamic node typing (both with and without this PR). At least last time I discussed it with people in Element, there was a desire to take a more holistic look at both dynamically changing the number of inputs and dynamically changing the types of inputs. Let me know if I'm missing something, but I don't think this PR brings us any further away from supporting those two cases, so it should be ok to make that as a separate change after the execution model is updated. |
@guill sounds goods. But I'm not sure if |
Can't get the tests to run locally, so this is dry-coded.
Closing this PR as I have separated out the core changes to ComfyUI to a new PR: #2666 The custom nodes from this PR have been relocated to https://github.com/BadCafeCode/execution-inversion-demo-comfyui Note that you will still have to manually enable "*" socket types to be able to use some of the nodes from that custom node pack. If you have been using this branch locally, I would appreciate your assistance in testing that new PR. While it passes all unit tests, I haven't had a chance to experiment with SDXL or video myself. |
This PR inverts the execution model -- from recursively calling nodes to
using a topological sort of the nodes. This change allows for
modification of the node graph during execution. This allows for two
major advantages:
"Mix Images" node has a mix factor of exactly 0.0, the second image
input doesn't even need to be evaluated (and visa-versa if the mix
factor is 1.0).
"node groups". Specifically, custom nodes can return subgraphs that
replace the original node in the graph. This is an incredibly
powerful concept. Using this functionality, it was easy to
implement:
a. Components (a.k.a. node groups)
b. Flow control (i.e. while loops) via tail recursion
c. All-in-one nodes that replicate the WebUI functionality
d. and more
All of those were able to be implemented entirely via custom nodes
without hooking or replacing any core functionality. Within this PR,
I've included all of these proof-of-concepts within a custom node pack.
In reality, I would expect some number of them to be merged into the
core node set (with the rest left to be implemented by custom nodes).
I made very few changes to the front-end, so there are probably some
easy UX wins for someone who is more willing to wade into .js land. The
user experience is a lot better than I expected though -- progress shows
correctly in the UI over the nodes that are being expanded.