comfyanonymous / ComfyUI

The most powerful and modular diffusion model GUI, api and backend with a graph/nodes interface.
https://www.comfy.org/
GNU General Public License v3.0
51k stars 5.35k forks source link

`/history ` and `/view` API are slow. #1971

Open Jannchie opened 10 months ago

Jannchie commented 10 months ago

I've found that executing on the GUI and getting the image out is fast, but there is a lot of overhead in getting the generation history and downloading the image when using the API.

$ python .\comfy_websocket.py
Generate Time Cost: 2.36s
Get Generate History Time Cost: 2.04s
Download Time Cost: 4.13s
nihiluis commented 7 months ago

Why is this closed? I think this is a valid issue, for me it's taking 8s even.

Gerschel commented 7 months ago

@nihiluis They closed their own issue. Perhaps they found out it had something to do with their hardware, for all we know.

Jannchie commented 7 months ago

Unfortunately, I forgot why I closed it.... It is true that this problem persists. I did not fix the problem on my side.

Jannchie commented 7 months ago

I guess I should reopen this issue. I've found that this problem only occurs the first time I call APIs like history.

Gerschel commented 7 months ago

How big is your history, do you have a ton of nodes in each history item. When you hit the endpoint, you have two options, the prompt id, or without prompt id, also, you can set a max number of items.

#from server.py PromptServer
        @routes.get("/history")
        async def get_history(request):
            max_items = request.rel_url.query.get("max_items", None)
            if max_items is not None:
                max_items = int(max_items)
            return web.json_response(self.prompt_queue.get_history(max_items=max_items))

        @routes.get("/history/{prompt_id}")
        async def get_history(request):
            prompt_id = request.match_info.get("prompt_id", None)
            return web.json_response(self.prompt_queue.get_history(prompt_id=prompt_id))

The self.prompt_queue is from a Class PromptQueue

#from main.py
server = server.PromptServer(loop)
q = execution.PromptQueue(server)

The PromptQueue has a history item:

#from execution.py
class PromptQueue:
    def __init__(self, server):
        self.server = server
        self.mutex = threading.RLock()
        self.not_empty = threading.Condition(self.mutex)
        self.task_counter = 0
        self.queue = []
        self.currently_running = {}
        self.history = {}
        self.flags = {}
        server.prompt_queue = self
#...

Which has a get_history method:

#inside of PromptQueue
def get_history(self, prompt_id=None, max_items=None, offset=-1):
        with self.mutex:
            if prompt_id is None:
                out = {}
                i = 0
                if offset < 0 and max_items is not None:
                    offset = len(self.history) - max_items
                for k in self.history:
                    if i >= offset:
                        out[k] = self.history[k]
                        if max_items is not None and len(out) >= max_items:
                            break
                    i += 1
                return out
            elif prompt_id in self.history:
                return {prompt_id: copy.deepcopy(self.history[prompt_id])}
            else:
                return {}

To be able to restore a history from a prior generation. It stores all the nodes, all their connections, all their outputs, all their settings, for every event.

How big is your node tree, and history. If you specify a prompt id, you wouldn't have to iterate through all the history items, under the hood, self.history is a dictionary (hash table), so it's look up is quick, what will slow it down, is having to iterate through each history and copy.