Guides

How to scale a ComfyUI workflow to thousands of outputs

A workflow that gives ten good images in the browser is not a production run yet. What changes at a thousand: batch size vs. queue, the API, several GPUs, rented hardware and the reject pile.

Updated October 19, 2026 · 10 min read

Ten test images from a ComfyUI workflow take a few clicks. A thousand need a different setup: ComfyUI batch processing through the queue rather than the button, a script that talks to the ComfyUI API, one process per GPU, rented hardware when your own cards run out, and a plan for the outputs you will throw away.

Written from practice: we run client workflows at volume on our own GPUs and, for large orders, on rented ones from Vast.ai.

Batch size vs. queue: two ways to batch generate in ComfyUI

Two separate knobs that often get confused:

  • Batch size is the batch_size input on Empty Latent Image (1 to 4096). The sampler processes all those latents in one pass.
  • The queue is the batch count next to the Run button, or several API calls. Each run is a separate job, executed one after another. Models stay loaded between runs while memory allows.
Batch sizeQueue (separate runs)
VRAMgrows with every latent; too large means out-of-memorysame as one run
Speedcan use the GPU better on small models and resolutionssmall overhead per run once models are loaded
Seedsone seed for the whole batchits own seed per run
Reproducing one outputsame seed and batch size, or LatentFromBatchrerun with its seed
On errorthe whole batch is lostone output is lost

ComfyUI generates noise for the whole batch from a single seed, so image 5 of a batch of 8 can only be reproduced as “seed X, batch of 8, index 4”. When you regenerate rejects later, separate runs with their own seeds are far easier.

Measure seconds per output, not per run. Raise the batch size on the actual card until VRAM is nearly full or the time per image stops dropping, then use the queue for volume. Video graphs almost always run one clip per job.

One trap: the UI seed widget’s control after generate setting (fixed, increment, randomize) is a frontend feature. Through the API the seed in the JSON is used as is, so your script must set it.

Prompts from a file, seed lists and input images

A thousand runs with the same prompt and seed is one output a thousand times; ComfyUI even caches an identical job and saves no new file. Volume comes from varying something:

  • Seeds. A fixed list, or a start value plus the job index, so any output can be reproduced.
  • A prompt matrix. Subject × style × background: 10 × 5 × 4 is already 200 combinations. Build it in a script.
  • Prompts from a file. Custom node packs do this inside the UI; Inspire Pack, for example, has Load Prompts From File. For large runs a script reading a text or CSV file is easier to control and resume.
  • Input images. For image-to-image, image-to-video or editing graphs, each input file is one job. Put files in ComfyUI’s input folder (or upload them with POST /upload/image) and set the file name in the LoadImage node.

Keep a manifest: one row per job with prompt, seed, input, output and status. It lets you resume after a crash and rerun rejects with new seeds.

Run a ComfyUI workflow programmatically through the API

ComfyUI is a web server, and the browser is just one of its clients.

  1. Export in API format: File → Export Workflow (API) (older versions: Save (API Format), visible after enabling dev mode options). The regular workflow JSON won’t be accepted. The API JSON maps node ids to class_type and inputs; a link like ["4", 0] means output 0 of node 4. Note the ids of the nodes you will change.
  2. Queue: POST /prompt with {"prompt": <API JSON>} returns a prompt_id. A broken graph gets HTTP 400 with node_errors before anything is queued.
  3. Wait: GET /history/{prompt_id} returns an empty object until the job finishes, then the outputs and a status. For live progress, open ws://host:8188/ws?clientId=<id> and send the same client_id with /prompt: you get progress, executing (with node: null when done), execution_success and execution_error.
  4. Download: each file comes with filename, subfolder and type; pass them to GET /view.

A minimal script, standard library only: every line of prompts.txt with every seed, spread over two servers.

import copy, json, time, urllib.parse, urllib.request
from pathlib import Path

SERVERS = ["127.0.0.1:8188", "127.0.0.1:8189"]  # one ComfyUI process per GPU
PROMPT_NODE, SEED_NODE = "6", "3"  # node ids from your API-format JSON
SEEDS = [1001, 1002, 1003]

workflow = json.loads(Path("workflow_api.json").read_text())
prompts = [p.strip() for p in Path("prompts.txt").read_text().splitlines() if p.strip()]
out = Path("out")
out.mkdir(exist_ok=True)

def call(server, path, payload=None):
    data = json.dumps(payload).encode() if payload is not None else None
    req = urllib.request.Request(f"http://{server}{path}", data=data,
                                 headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())

jobs = []
for i, text in enumerate(prompts):
    for seed in SEEDS:
        wf = copy.deepcopy(workflow)
        wf[PROMPT_NODE]["inputs"]["text"] = text
        wf[SEED_NODE]["inputs"]["seed"] = seed
        server = SERVERS[len(jobs) % len(SERVERS)]
        prompt_id = call(server, "/prompt", {"prompt": wf})["prompt_id"]
        jobs.append((server, prompt_id, f"{i:04d}_{seed}"))

for server, prompt_id, tag in jobs:
    while not (history := call(server, f"/history/{prompt_id}")):
        time.sleep(2)
    entry = history[prompt_id]
    if entry["status"]["status_str"] != "success":
        print("failed:", tag)
        continue
    for node_output in entry["outputs"].values():
        files = [f for v in node_output.values() if isinstance(v, list)
                 for f in v if isinstance(f, dict) and f.get("type") == "output"]
        for f in files:
            query = urllib.parse.urlencode({k: f[k] for k in ("filename", "subfolder", "type")})
            with urllib.request.urlopen(f"http://{server}/view?{query}") as resp:
                (out / f"{tag}_{f['filename']}").write_bytes(resp.read())

6 and 3 are the prompt and KSampler nodes of ComfyUI’s default workflow; yours will differ, and some samplers call the field noise_seed. A job that fails validation raises HTTPError; its body holds the node_errors.

Notes from real runs:

  • History lives in the ComfyUI process memory. After a restart the files remain, but the job-to-file mapping is gone. Write each prompt_id to the manifest right away.
  • For thousands of jobs, queue in chunks and give the next job to the server with the shortest queue (GET /queue).
  • ComfyUI has no login. Don’t expose its port; use an SSH tunnel or an authenticating proxy.

ComfyUI on multiple GPUs

One ComfyUI process generates on one GPU. For volume, run one process per card, each on its own port:

python main.py --cuda-device 0 --port 8188 --output-directory /data/out0 --temp-directory /data/tmp0
python main.py --cuda-device 1 --port 8189 --output-directory /data/out1 --temp-directory /data/tmp1
  • --cuda-device makes only that card visible to the process.
  • Separate output and temp folders: ComfyUI clears temp on startup and numbers files by scanning the output folder, so shared folders cause collisions.
  • The models folder can be shared, but each process loads its own copy into VRAM and system RAM.

Since late May 2026 ComfyUI also has a built-in MultiGPU CFG Split node that splits one generation across identical GPUs. Per the documentation it needs identical Ampere-or-newer cards, works with a listed set of models (WAN 2.2 and SDXL among them) and gives nothing at CFG 1, which rules out distilled setups such as Lightning-type LoRAs. For a thousand outputs, separate processes are simpler, and one crash doesn’t stop the others.

More machines just mean more addresses in SERVERS, with the same ComfyUI version, node commits and models everywhere.

ComfyUI in the cloud vs. local: limits to check

Locally you control everything, but one or two cards set the ceiling. Hosted ComfyUI services and serverless GPU APIs remove the hardware question and add their own limits. Check:

  • Custom nodes and models: often only an approved set of nodes; uploads of your own LoRA may be limited.
  • Parallelism: how many jobs per account run at once. That is your throughput.
  • Timeouts: the maximum time per job, which long video graphs can hit.
  • Cold starts: on serverless, the first job after idle loads the models.
  • Billing unit: GPU seconds, jobs or credits. It decides what your rejects cost.

Renting a whole GPU machine sits in between: any nodes and models, but the setup is yours.

ComfyUI on Vast.ai: what renting GPUs taught us

Vast.ai is a marketplace of GPU hosts; for large orders we run several instances there in parallel. Lessons that cost us time:

  • The disk is fixed at creation. Vast doesn’t let you resize it later, and storage is billed even while the instance is stopped. A video graph’s models take tens of gigabytes; our rule of thumb is about 1.5 times the total model weight plus room for outputs, never below 50 GB.
  • A cold start takes minutes, and you pay for them. Container start, ComfyUI and custom node install, then the model download, usually the longest part and dependent on the host network. One instance should take a long share of the run.
  • Hosts are not all the same. Our video graph upscales with NVIDIA RTX Video Super Resolution, and on some hosts that node would not initialize although CUDA worked. Now we test such nodes right after boot, before downloading the models, and switch hosts if they fail. Vast’s reliability score is a filter, not a guarantee.
  • Pin versions. Custom nodes from git at fixed commits, and ComfyUI pinned per workflow. We had a model loader fail because the pinned ComfyUI commit predated support for that model. Unpinned installs eventually change the output mid-run.
  • Assume the instance can disappear. Copy outputs off the machine as you go (we use cloud storage) and keep the manifest elsewhere.

Quality control at volume

At a thousand outputs the question is what share is unusable and who finds it.

  1. Trial batch first: 20 to 50 outputs drawn from across the prompt matrix and inputs, not the first 50 rows. We agree it with the client before the volume run.
  2. Measure the reject rate. If one in five fails, generate about 1,250 for 1,000 good ones, and rework the worst prompts.
  3. Freeze the setup. Changing models or parameters mid-run means a new trial batch.
  4. Review by eye: hands and faces, artifacts, broken text, character drift, flicker and seams in video. We review manually, in a grid sorted by prompt, so a bad prompt shows up as a block.
  5. Regenerate rejects from the manifest with new seeds.

What 1,000 generations really cost

GPU cost   = t × N ÷ (1 − r) ÷ 3600 × P
Total cost ≈ GPU cost + setup + trial batch + review

t is seconds per output, N the good outputs you need, r the reject share, P the GPU hour price.

  • t can’t be borrowed from someone else’s benchmark: it depends on model, resolution, steps, graph and card. Measure it on the trial batch.
  • Several GPUs shorten wall-clock time, not GPU hours.
  • Setup is cold starts times instances, plus hours of installing and debugging. At small volumes it dominates.
  • Review is human time and grows with N.

Our price for images starts at $0.10 per image on any checkpoint and LoRA. Setup and the trial batch are included; the minimum order is $50.

When it’s easier to hand it to us

Have a working workflow and need volume, not a side job in GPU operations? Send it to us. We install the template as is, with nodes at pinned commits and models from Hugging Face, Civitai or a direct link, make a trial batch for your approval, run the volume on our own or rented GPUs, review by hand and deliver an archive or a folder link. If a model is paid, closed or its license forbids commercial use, we say so before the start (see Civitai licenses).

Send the workflow, the task and the volume through the form below.

 FAQ

Questions

Q01 Is a bigger batch size faster than queueing more runs?

Sometimes. A batch can use the GPU better on small models and low resolutions, but it costs VRAM, shares one seed across the batch and fails as a whole. Measure seconds per output on your card for both and keep whichever is lower.

Q02 Can ComfyUI use two GPUs for one image?

By default one ComfyUI process works on one GPU. Recent versions include a MultiGPU CFG Split node that splits one generation across identical cards, but it only helps when CFG is above 1. For volume, one process per card is simpler and keeps working if one process crashes.

Q03 Does ComfyUI have an API?

Yes. The ComfyUI server that the browser talks to is the API: POST /prompt to queue a workflow exported in API format, /history to read results, /view to download files and a websocket for progress. It has no login of its own, so keep it off the open internet.

Q04 How long does a rented GPU take to become ready for ComfyUI?

Minutes rather than seconds: the container has to start, ComfyUI and custom nodes have to install, and the models have to download. Most of that time goes on the models and depends on the host network, and you pay for the GPU the whole time.

Q05 What does 1,000 images cost if you run them for us?

Our price starts at $0.10 per image on any checkpoint and LoRA, so from $100 for 1,000 images. Setup and the trial batch are included, and the minimum order is $50.

 Contact

Discuss a project

Tell us about the template and the volume. We will get back to you via the contact you leave.

By sending the request you agree to the privacy policy.