Files
ttulttul-ComfyUI-Modal/docs/LEARNINGS.md
T

6.5 KiB
Raw Blame History

Learnings

2026-04-17

  • The workflow metadata already rides along in extra_data.extra_pnginfo.workflow, so the backend can discover node.properties.is_modal_remote without any extra frontend metadata channel.
  • A single wildcard-output proxy node is not enough for server-side graph rewriting. ComfyUI validates downstream link indices and output arity against the replacement class, so the backend has to generate proxy node classes that mirror the original node’s output signature before validation runs.
  • Keeping the sync engine content-addressable makes prompt rewrites cheap after the first upload. The route can block on sync safely as long as repeated requests collapse to a hash marker check.
  • Hashing and zipping the entire custom_nodes/ tree inside the queue route is too expensive for COMFY_MODAL_EXECUTION_MODE=local. Local mode should skip custom-node bundle sync by default, with an explicit override when that path needs to be exercised.
  • When rewriting a prompt node into a proxy, never reuse the same input dict for both prompt_node["inputs"] and original_node_data["inputs"]. Inserting original_node_data back into the live prompt inputs creates a recursive structure that breaks ComfyUI cache signature hashing with a RecursionError.
  • Queue-time validation is necessary for remote nodes that still receive local Comfy runtime objects. Even if a node is marked remote, inputs like MODEL or CONDITIONING cannot be serialized across the current boundary, so the prompt should be rejected before execution rather than failing inside the serializer.
  • Per-node proxying is not enough for real ComfyUI sampler graphs. Connected remote-marked nodes must be partitioned into a single remote component so internal edges like MODEL, CLIP, and CONDITIONING stay inside the remote subgraph and only the true local/remote boundary has to be transport-safe.
  • The local fallback runtime cannot call PromptExecutor.execute() inline from inside a proxy node, because ComfyUI is already running inside an active asyncio loop. Running remote subgraph execution in a worker thread avoids nested-event-loop failures while still reusing ComfyUI’s executor.
  • Modal objects defined in-process are not directly callable from an arbitrary long-running app like ComfyUI unless they are inside a hydrated app.run() session or looked up from a deployed app with modal.Cls.from_name(...). Remote mode should prefer deployed lookup and only fall back to app.run() when no deployment is available.
  • ComfyUI loads custom node packs from directories under synthetic module names derived from filesystem paths, and for directory-based packs that module key is not guaranteed to be a valid Python import path. Modal container startup requires a real importable module name for @app.cls services, so the cloud entrypoint has to live in a separate, stable module such as comfyui_modal_sync_cloud.py rather than on the custom-node package module itself.
  • Shipping the ComfyUI source tree into the Modal image is not enough by itself. The container also needs ComfyUI's import-time Python dependencies such as psutil, aiohttp, einops, torchsde, torchvision, and related runtime packages, or the remote worker fails before it can execute any graph nodes.
  • The frontend can reuse ComfyUI's native executing, execution_error, and execution_success websocket events for remote component visuals as long as the queue route also publishes the representative-node-to-original-node mapping after rewrite. That keeps the UI in sync with actual proxy execution instead of inventing a separate polling channel.
  • When filtering the ComfyUI checkout for the Modal image, only exclude top-level runtime asset directories like models/ and output/. A blanket "ignore any path part named models" rule will also delete real Python packages such as comfy/ldm/models, causing import failures inside the container.
  • Importing nodes.py alone is not enough inside the Modal container. The remote worker also has to run nodes.init_extra_nodes(...) once so built-in comfy_extras nodes such as EmptyFlux2LatentImage exist in NODE_CLASS_MAPPINGS, and then run init_external_custom_nodes() for each extracted custom-node bundle root it exposes.
  • A bare PromptExecutor(_NullPromptServer()) is not equivalent to real ComfyUI startup. The headless worker must pass the same cache configuration that main.py uses, or execution eventually crashes when the executor polls caches with self.cache_args["ram"].
  • If /modal/queue_prompt does expensive preparatory work before it returns, the frontend can keep ComfyUI's own queue/execution indicators alive by dispatching temporary synthetic status, execution_start, and executing events, then letting the real websocket events take over once the prompt is actually queued.
  • The synthetic frontend events have to match ComfyUI's websocket adapter shapes exactly, not just semantically. In practice that means status detail must be the inner status payload, executing detail should be the display node id string, and queue-list initialization state benefits from a matching "Waiting for a machine" notification payload keyed by prompt id.
  • Even with correctly-shaped synthetic events, ComfyUI's built-in queue UI may still not present an obvious "workflow running" state during long pre-queue Modal setup. The extension should own a separate global activity badge so users have explicit feedback while the remote runtime is still being prepared.
  • Synced asset paths cannot stay as abstract /assets/... markers inside the Modal worker. Remote execution needs those markers materialized to real absolute files under the mounted storage root, and ComfyUI's folder_paths helpers need an absolute-path fast path so loader nodes can still resolve them.
  • A local filesystem mirror is not enough in COMFY_MODAL_EXECUTION_MODE=remote. Queue-time sync must upload assets into the actual named Modal volume via the local SDK, and the remote worker must call vol.reload() before execution so warm containers can see newly uploaded files.
  • Modal volume SDK calls should not run directly on the aiohttp request event loop. Running them in a worker thread avoids AsyncUsageWarning, and a missing path from Volume.listdir() should be treated as an ordinary "not uploaded yet" cache miss instead of a hard failure.
  • The Modal container can pick up a non-package utils module before ComfyUI initializes. Preloading ComfyUI's own top-level utils package from the bundled source tree prevents utils.install_util import failures during init_extra_nodes(...).