Skip to content

Latest commit

 

History

History
652 lines (603 loc) · 14.2 KB

File metadata and controls

652 lines (603 loc) · 14.2 KB

ComfyUI-Tensor-Bridge

Seamlessly transfer Latents, Images, and Tensors between separate ComfyUI instances over the internet.

Banner Image

ComfyUI-Tensor-Bridge allows you to break the VRAM barrier by distributing your workflow across multiple machines. Connect your local PC to a Cloud GPU (RunPod, Colab, Kaggle), or link two local GPUs together.

Why use this?

1. Hybrid Setup (Local + Cloud)

You have a PC with low VRAM, but you want to run massive models like Flux or Qwen Image Edit.

  • Local: Run the lightweight text encoding, prompt logic, and simple previewing.
  • Cloud: Send the lightweight Latent tensor to a Kaggle/RunPod instance to handle the heavy KSampler/Diffusion.
  • Result: Get the image back on your local machine instantly.

2. Privacy & Control

Keep your prompts, LoRAs, and control logic on your private local machine. Only send the necessary tensors to the cloud for processing.

3. Distributed Pipelines

Run generation on Machine A and heavy Upscaling/Face-Restoration on Machine B simultaneously.


Features

  • Zero-Lag Polling: Receiver nodes wait intelligently for new data, consuming 0 resources while idle.
  • Synchronization: Unique trigger pin ensures your local workflow waits exactly until the remote worker is finished.
  • Auto-Cleaning: Data is consumed (deleted) upon reading, ensuring you never accidentally process old files.
  • Ngrok Compatible: Works perfectly through Ngrok tunnels for easy Local-to-Cloud networking.
  • Type Safety: Explicit nodes for Latents and Images to prevent workflow errors.

Installation

Method 1: Git Clone

Navigate to your ComfyUI custom_nodes directory and run:

git clone https://github.com/YOUR_USERNAME/ComfyUI-Tensor-Bridge.git
cd ComfyUI-Tensor-Bridge
pip install -r requirements.txt

Method 2: ComfyUI Manager

Coming soon to the ComfyUI Manager Registry. (For now, you can use "Install via Git URL" in the Manager).


How It Works: The "Ping-Pong" Workflow

To create a continuous loop where Instance A generates and Instance B processes, follow this pattern.

Instance A: The Controller (e.g., Local PC)

This instance starts the generation, sends data away, and waits for the result.

The Workflow:

  1. Generate: Create a Latent (Empty Latent / KSampler).
  2. Send: Connect to Bridge Sender. Point URL to Instance B.
  3. Wait: Connect the Sender to a Bridge Receiver.
    • Note: Connect the Sender Output to the Receiver trigger input. This forces ComfyUI to wait for the upload to finish before trying to download.
  4. Save: Save the final image.

Instance A Screenshot

📄 Click to view Instance A (Controller) JSON
{
  "id": "ddd225da-eb4c-4aca-9eab-60aef0a3cc0a",
  "revision": 0,
  "last_node_id": 6,
  "last_link_id": 3,
  "nodes": [
    {
      "id": 4,
      "type": "EmptyLatentImage",
      "pos": [
        865.8529335937498,
        42.672167968749264
      ],
      "size": [
        270,
        106
      ],
      "flags": {},
      "order": 0,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "links": [
            1
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "EmptyLatentImage"
      },
      "widgets_values": [
        256,
        256,
        1
      ]
    },
    {
      "id": 2,
      "type": "BridgeSenderLatent",
      "pos": [
        1203.2094414062492,
        40.30652343749932
      ],
      "size": [
        270,
        82
      ],
      "flags": {},
      "order": 1,
      "mode": 0,
      "inputs": [
        {
          "name": "latent",
          "type": "LATENT",
          "link": 1
        }
      ],
      "outputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "links": [
            2
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "BridgeSenderLatent"
      },
      "widgets_values": [
        "http://127.0.0.1:8189/",
        "latent_pipe"
      ]
    },
    {
      "id": 5,
      "type": "BridgeReceiverImage",
      "pos": [
        1561.1340585937496,
        37.51454296874929
      ],
      "size": [
        270,
        82
      ],
      "flags": {},
      "order": 2,
      "mode": 0,
      "inputs": [
        {
          "name": "trigger",
          "shape": 7,
          "type": "*",
          "link": 2
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            3
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "BridgeReceiverImage"
      },
      "widgets_values": [
        "image_pipe",
        60
      ]
    },
    {
      "id": 6,
      "type": "PreviewImage",
      "pos": [
        1905.9306484374993,
        36.87503906249927
      ],
      "size": [
        140,
        246
      ],
      "flags": {},
      "order": 3,
      "mode": 0,
      "inputs": [
        {
          "name": "images",
          "type": "IMAGE",
          "link": 3
        }
      ],
      "outputs": [],
      "properties": {
        "Node name for S&R": "PreviewImage"
      },
      "widgets_values": []
    }
  ],
  "links": [
    [
      1,
      4,
      0,
      2,
      0,
      "LATENT"
    ],
    [
      2,
      2,
      0,
      5,
      0,
      "LATENT"
    ],
    [
      3,
      5,
      0,
      6,
      0,
      "IMAGE"
    ]
  ],
  "groups": [],
  "config": {},
  "extra": {
    "ds": {
      "scale": 0.7513148009015778,
      "offset": [
        -447.25343359374904,
        317.70128125000076
      ]
    },
    "workflowRendererVersion": "LG",
    "frontendVersion": "1.35.9"
  },
  "version": 0.4
}

Instance B: The Worker (e.g., Cloud GPU)

This instance sits in a loop, waiting for work to arrive.

The Workflow:

  1. Wait: Start with Bridge Receiver.
  2. Process: Do your heavy lifting (Upscaling, Sampling, etc.).
  3. Return: End with Bridge Sender. Point URL back to Instance A.

Crucial Setup for Instance B: Set the run count (the number to the right of the Run button) to a very high value (e.g., 10,000). This ensures the workflow automatically re-runs after each execution, effectively acting as an auto-queue.

Instance B Screenshot

📄 Click to view Instance B (Worker) JSON
{
  "id": "fa859a67-69e7-47d5-9993-33692ec4225e",
  "revision": 0,
  "last_node_id": 10,
  "last_link_id": 10,
  "nodes": [
    {
      "id": 1,
      "type": "BridgeReceiverLatent",
      "pos": [
        689.5119570312503,
        -184.52194492187488
      ],
      "size": [
        270.5328125,
        82
      ],
      "flags": {},
      "order": 0,
      "mode": 0,
      "inputs": [
        {
          "name": "trigger",
          "shape": 7,
          "type": "*",
          "link": null
        }
      ],
      "outputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "links": [
            1
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "BridgeReceiverLatent"
      },
      "widgets_values": [
        "latent_pipe",
        3600
      ]
    },
    {
      "id": 4,
      "type": "VAELoader",
      "pos": [
        693.0006328125003,
        -30.694219531249892
      ],
      "size": [
        270,
        58
      ],
      "flags": {},
      "order": 1,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "VAE",
          "type": "VAE",
          "links": [
            2
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "VAELoader"
      },
      "widgets_values": [
        "pixel_space"
      ]
    },
    {
      "id": 6,
      "type": "PreviewImage",
      "pos": [
        1876.7953479758548,
        -145.3724285866478
      ],
      "size": [
        210,
        258
      ],
      "flags": {},
      "order": 6,
      "mode": 0,
      "inputs": [
        {
          "name": "images",
          "type": "IMAGE",
          "link": 5
        }
      ],
      "outputs": [],
      "properties": {
        "Node name for S&R": "PreviewImage"
      },
      "widgets_values": []
    },
    {
      "id": 3,
      "type": "VAEDecode",
      "pos": [
        1038.4134822798299,
        -117.34515337357945
      ],
      "size": [
        140,
        46
      ],
      "flags": {},
      "order": 2,
      "mode": 0,
      "inputs": [
        {
          "name": "samples",
          "type": "LATENT",
          "link": 1
        },
        {
          "name": "vae",
          "type": "VAE",
          "link": 2
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            3,
            9
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "VAEDecode"
      }
    },
    {
      "id": 2,
      "type": "ImageScale",
      "pos": [
        1231.9175151633515,
        -40.11736693892045
      ],
      "size": [
        270,
        130
      ],
      "flags": {},
      "order": 3,
      "mode": 0,
      "inputs": [
        {
          "name": "image",
          "type": "IMAGE",
          "link": 3
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            10
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "ImageScale"
      },
      "widgets_values": [
        "nearest-exact",
        512,
        512,
        "disabled"
      ]
    },
    {
      "id": 5,
      "type": "BridgeSenderImage",
      "pos": [
        1557.4281343039772,
        -145.16733529829528
      ],
      "size": [
        270,
        82
      ],
      "flags": {},
      "order": 5,
      "mode": 0,
      "inputs": [
        {
          "name": "image",
          "type": "IMAGE",
          "link": 10
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            5
          ]
        }
      ],
      "properties": {
        "Node name for S&R": "BridgeSenderImage"
      },
      "widgets_values": [
        "http://127.0.0.1:8188",
        "image_pipe"
      ]
    },
    {
      "id": 10,
      "type": "PreviewImage",
      "pos": [
        1271.16041295519,
        -383.7716443956613
      ],
      "size": [
        210,
        258
      ],
      "flags": {},
      "order": 4,
      "mode": 0,
      "inputs": [
        {
          "name": "images",
          "type": "IMAGE",
          "link": 9
        }
      ],
      "outputs": [],
      "properties": {
        "Node name for S&R": "PreviewImage"
      },
      "widgets_values": []
    }
  ],
  "links": [
    [
      1,
      1,
      0,
      3,
      0,
      "LATENT"
    ],
    [
      2,
      4,
      0,
      3,
      1,
      "VAE"
    ],
    [
      3,
      3,
      0,
      2,
      0,
      "IMAGE"
    ],
    [
      5,
      5,
      0,
      6,
      0,
      "IMAGE"
    ],
    [
      9,
      3,
      0,
      10,
      0,
      "IMAGE"
    ],
    [
      10,
      2,
      0,
      5,
      0,
      "IMAGE"
    ]
  ],
  "groups": [],
  "config": {},
  "extra": {
    "ds": {
      "scale": 0.9090909090909091,
      "offset": [
        -555.9283817051913,
        601.8724256456611
      ]
    },
    "workflowRendererVersion": "LG",
    "frontendVersion": "1.35.9"
  },
  "version": 0.4
}

Node Documentation

📡 Bridge Sender (Latent / Image)

Uploads data to a remote ComfyUI instance.

  • Input: The Latent or Image to send.
  • URL: The full address of the receiving instance (e.g., https://my-node.ngrok-free.app).
  • Channel: A unique name (ID) for this data stream (e.g., pipe_1). Must match the Receiver.

📡 Bridge Receiver (Latent / Image)

Waits for data to appear, loads it, and deletes the file.

  • Channel: Must match the Sender.
  • Timeout: How long to wait (in seconds) before failing.
  • Trigger (Optional): Connect any output here. The Receiver will strictly wait for the connected node to finish before starting its polling loop. Essential for single-workflow loops.

Best Practices

  1. Unique Channels: If you run multiple distinct workflows, use different channel names (e.g., face_fix, background_gen) to avoid cross-talk.
  2. Timeouts: If your Cloud GPU takes 5 minutes to generate, set the timeout_sec on your Local Receiver to 600 (10 minutes) to avoid premature timeout errors.

Advanced: Daisy-Chaining & Multi-Pipelines

You are neither limited to a simple Send/Receive loop nor to just two instances. Since the architecture uses HTTP/Channels, you can build complex distributed pipelines involving 3, 4, or more machines.

The "Pipeline" Workflow (A -> B -> C -> A)

Imagine a setup where you have specialized hardware for different tasks:

  1. Local PC: Handles Prompting & ControlNet Preprocessing.
  2. Cloud Instance 1 (T4 GPU): Handles standard SDXL Generation.
  3. Cloud Instance 2 (A100 GPU): Handles massive Video Generation or Ultimate Upscaling.

How to set it up:

  • Node A (Mac): Sends Latent to Instance 1 (Channel: step_1). Waits for final_result.
  • Node B (Instance 1): Receives step_1 -> Generates -> Sends to Instance 2 (Channel: step_2).
  • Node C (Instance 2): Receives step_2 -> Upscales -> Sends to Mac (Channel: final_result).

All intermediate nodes (B and C) should be set to Auto Queue.

The "Parallel" Workflow (A -> B & A -> C)

Your Local controller can dispatch tasks to multiple workers simultaneously.

  • Sender 1: Sends to Worker B (Channel: face_job).
  • Sender 2: Sends to Worker C (Channel: background_job).
  • Receiver: Waits for both to return before compositing the final image.

Security Warning

This node uses torch.load and torch.save (Pickle) to transfer data.

  • Risk: Pickle files can execute arbitrary code.
  • Rule: Only receive data from sources you trust. Do not expose a Receiver node on a public URL without authentication if you don't trust the sender.