Part of AI Tools & Automation — automating real work with local AI models.
12_ernie_gallery.py · 12_comfyui_ernie_benchmark.py. Every image below is real, unedited output.

Every blog post needs a hero image, and buying stock or paying a cloud generator adds up fast. With ComfyUI on Windows 11 and an AMD GPU, you generate images locally, drive the whole thing from Python, and keep the marginal cost at zero. This is a deep, practical guide: what ComfyUI is, how to set it up on AMD, how to call a workflow programmatically, and — most importantly — how good the images actually are, measured on 20 real generations.
- ComfyUI runs on AMD via ROCm and exposes a clean HTTP API for automation.
- ERNIE-Image-Turbo renders in ~7 seconds per image (eight steps).
- Across 10 varied prompts, CLIP adherence averaged 30.9 — a strong match, with concrete scenes scoring higher than abstract ones.
- In a 10-prompt text test, the model rendered the requested text correctly every time, and cleanly in 7 of 10.
What is ComfyUI, and what does it look like?
ComfyUI is a node-based interface for image and video models. Instead of a single “generate” button, you build a small graph: a node loads the model, another encodes your prompt, a sampler denoises the image, and a final node decodes and saves it. Data flows along the wires between nodes, which makes the whole pipeline visible and editable.
Many people have never seen it, so here is the interface with a basic text-to-image graph loaded:

The power of this design is reproducibility. A workflow is just JSON, so you can save it, share it, and — as we will see — send it to the server from a script. What you build visually, you can also automate exactly.
Why generate blog images locally?
Cost is the obvious reason. Stock libraries charge per image and cloud generators charge per call, while a local model turns that into a one-time hardware cost. After the GPU is paid for, ten images cost the same as a thousand.
Control is the subtler reason. You own the model, the workflow, and the style, so every hero image can match your brand. And because generation runs offline, an unpublished topic never leaks to a third-party service before you post it. For a blog that ships regularly, that combination of zero marginal cost and full privacy is hard to beat.
Setting up ComfyUI on Windows 11 with AMD
The one tricky part on AMD is the backend. ComfyUI needs a build of PyTorch that talks to the GPU through ROCm rather than CUDA. Once that is in place, the rest is standard.
The high-level steps are:
- Install the portable ComfyUI build, then replace its PyTorch with a ROCm-enabled wheel that matches your card (gfx1100 for the W7900).
- Place the model files in
ComfyUI/models: the diffusion model underdiffusion_models/, the text encoder undertext_encoders/, and the VAE undervae/. - Start the server. On launch it reports the device it found.
# start ComfyUI (portable build with ROCm torch)
python_embededpython.exe -s ComfyUImain.py --listen 127.0.0.1 --port 8188
When it works, the log confirms the GPU:
device: cuda:0 AMD Radeon PRO W7900 : native
The cuda:0 label is just how the ROCm runtime presents the card — the work runs on the AMD GPU. From here, everything happens over the HTTP API on port 8188.
The ERNIE-Image-Turbo workflow
Standard diffusion models need 20–50 sampling steps. Turbo-distilled models slash that. ERNIE-Image-Turbo renders in just eight steps at a CFG of one, which is why each image lands in seconds. The workflow used here also chains a small local language model that expands a short prompt into a richer description before rendering, so a one-line idea becomes a detailed image brief automatically.
In node terms, the graph is: a UNET loader for the ERNIE model, a CLIP loader for the text encoder, a VAE loader, two CLIP-text-encode nodes (positive and negative), an empty latent, a KSampler at eight steps, a VAE decode, and a save node. The prompt-enhancement branch sits in front of the positive encoder.
ROCm specifics on the W7900
A few details make or break the AMD setup. The card is gfx1100 (RDNA3), so the ROCm PyTorch build must target that architecture. On Windows, the portable build ships a matching wheel, but if you assemble your own environment, pin the ROCm torch version to one that lists gfx1100 support. Keep an eye on VRAM too: at 48 GB the W7900 has ample room for image models, so you rarely need the low-VRAM flags, but if you also run an LLM on the same card, unload it first — two frameworks fighting over the GPU is the most common cause of a failed load.
Free ebook
Free AI Video, Generated Locally
Working scripts and measured benchmarks. Free.
No spam. Unsubscribe at any time.
API format versus UI format
ComfyUI workflows come in two shapes, and the difference trips people up. The UI format is what the interface saves by default: it includes node positions, colours, and layout, so it can redraw the graph. The API format is a leaner object that maps node IDs to their class and inputs, with no visual data. The server’s /prompt endpoint expects the API format.
To export it, open your workflow in ComfyUI, enable “dev mode” in the settings, and choose Save (API Format). You get a JSON file you can load and modify from a script, exactly as the example above does. Keep both versions: the UI file to edit visually, the API file to automate.
Calling the workflow programmatically
This is where ComfyUI becomes an image API. The server accepts a workflow in “API format” — a JSON object mapping node IDs to their class and inputs. You load that JSON once, change the prompt and seed, POST it to /prompt, and poll /history until the image is ready. Then you fetch the file from /view.
import json, time, random
from urllib import request, parse
SERVER = "http://127.0.0.1:8188"
def post(path, payload):
req = request.Request(f"{SERVER}{path}", data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"})
return json.loads(request.urlopen(req).read())
def get(path):
return json.loads(request.urlopen(f"{SERVER}{path}").read())
def generate(workflow, prompt, seed):
workflow["88:94"]["inputs"]["value"] = prompt # the prompt node
workflow["88:70"]["inputs"]["seed"] = seed # the KSampler seed
prompt_id = post("/prompt", {"prompt": workflow})["prompt_id"]
while True: # wait for completion
hist = get(f"/history/{prompt_id}")
if prompt_id in hist and hist[prompt_id].get("outputs"):
outputs = hist[prompt_id]["outputs"]
break
time.sleep(0.5)
# find the saved image and download the bytes
info = next(o["images"][0] for o in outputs.values() if "images" in o)
q = parse.urlencode({"filename": info["filename"],
"subfolder": info.get("subfolder", ""),
"type": info.get("type", "output")})
return request.urlopen(f"{SERVER}/view?{q}").read() # PNG bytes
workflow = json.load(open("image_ernie_image_turbo.json", encoding="utf-8"))
png = generate(workflow, "a serene mountain landscape at golden hour", random.randint(1, 2**31))
open("hero.png", "wb").write(png)
That is the entire integration surface. A publishing script loads the workflow, sets a prompt built from the post title, and gets back PNG bytes it can attach to the article — with no clicking in the ComfyUI interface at all.
Node by node: what the JSON actually contains
It helps to know which node to poke. In the API JSON, each key is a node ID and each value has a class_type and an inputs map. The inputs are either literal values or a two-element link, ["source_node_id", output_index], that wires one node’s output into another. To automate, you change the literals:
- The prompt lives in a text node’s
valueortextinput — set it to your topic. - The seed sits on the KSampler; randomise it for variety, or fix it to reproduce an exact image.
- Width, height, and length live on the empty-latent node.
- Steps, CFG, sampler, and scheduler are all KSampler inputs.
Everything else — the model loaders, the VAE, the wiring — you leave untouched. That is why one saved workflow serves as a template for thousands of images: you only ever change a handful of literal inputs.
Tracking progress over WebSocket
Polling /history is simple, but for long jobs a WebSocket gives live progress. ComfyUI pushes execution events on ws://127.0.0.1:8188/ws, including per-node progress and a “done” signal for your prompt. For image generation at seven seconds a piece, polling is fine; for video or big batches, the socket lets you show a progress bar and detect failures immediately instead of waiting on a timeout.
The gallery — 10 images, 10 prompts
Numbers are abstract; images are not. Here are ten real, unedited outputs, each with the exact prompt that produced it. Browse them and judge the quality yourself — that is, after all, how you will use the model.










Objective quality — prompt adherence
Eyeballing is fine, but a number helps. For each image, we computed a CLIP adherence score: how closely the image matches its prompt, measured as the cosine similarity between CLIP’s image and text embeddings, scaled to a 0–100 range. It is the standard, model-agnostic way to ask “did the picture match the words?”

A quick note on reading these numbers, because they are not percentages. For CLIP, scores in the 25–36 range signal a strong match — CLIP rarely goes much higher even for perfect images. The pattern is what matters. Concrete, photographable scenes scored highest: the cute robot watering a plant (36.0) and the cyberpunk food stall (35.6) topped the list. Abstract or conceptual prompts scored lower — the “friendly AI assistant helping a developer” (24.3) is a vaguer idea, so both the model and CLIP have more room to disagree. The takeaway is practical: the more concrete your prompt, the more faithfully the model renders it.
The text test — can it render the words you ask for?
Rendering legible text has long been the hardest task for image models, and it is exactly what a blog hero often needs — a title, a logo, a sign. So we ran a dedicated experiment: ten prompts, each asking for a specific piece of text, then a careful visual check of what actually appeared on the image.
The result was better than expected. All ten requested strings rendered correctly and legibly. Seven were completely clean; the other three were correct but picked up an extra artifact — a garbled subtitle, a duplicated word, or gibberish “code” in the background — usually where the prompt implied a second text area.
To be concrete, here are the exact prompts behind three of these images — this is precisely what went into the workflow’s prompt node:
# text_01
A minimalist tech poster with the large bold text "LOCAL AI" centered, clean design
# text_08
A shop window poster with "SALE 50%" in red letters
# text_05
A book cover titled "Offline LLMs" in large serif letters
And here is what the model produced — browse and judge the type quality yourself:






Here is the full scorecard from the visual check:
| Requested text | Rendered? | Notes |
|---|---|---|
| LOCAL AI | ✓ correct | Clean poster |
| bestin-it.com | ✓ correct | Exact, including the dot |
| GPU POWER | ✓ correct | Clean neon sign |
| RUN IT LOCAL | ✓ correct | Clean t-shirt print |
| Offline LLMs | ✓ correct | Title right; garbled subtitle added |
| AI 2026 | ✓ correct | Right; an extra “AI” duplicated |
| Hello World | ✓ correct | Right; gibberish “code” in background |
| SALE 50% | ✓ correct | Clean, including the % sign |
| QWEN | ✓ correct | Clean minimalist logo |
| Open Source | ✓ correct | Clean sticker with a heart |
Score: 10/10 legible and correct, 7/10 completely clean. The practical lesson is to keep text prompts tight. Ask for one short string and a single surface — “a poster with the text X” — and you will almost always get clean, correct type. Prompts that imply a second text area (a subtitle, a screen full of code) are where the stray artifacts creep in.
Render speed, briefly
Speed is not the headline here, but it is worth a line. After a one-time warm-up while the model loads, each image rendered in about 7.3 seconds at 608×800 — roughly eight images a minute. That is fast enough that quality, not throughput, is the thing to optimise. Keep the server running so you pay the warm-up only once.
Prompt enhancement in the workflow
Short prompts make weak images, so the workflow routes your prompt through a small local language model that expands it into a fuller description before rendering. “A robot reading in a library” becomes a paragraph with lighting, composition, and mood — and the image quality follows. Because the enhancer runs locally too, this step adds no cloud dependency and no extra cost.
Batch generation for a whole blog
The real payoff is automation. A publishing script reads each post’s title, builds a prompt, and calls the API to render a matching hero image. Run it across a backlog and every draft gets art in one pass.
posts = ["Local AI on a laptop", "Whisper transcription on AMD", "Content remix with a local LLM"]
for title in posts:
prompt = f"A clean editorial blog hero image about: {title}, minimal, modern"
png = generate(workflow, prompt, seed=random.randint(1, 2**31))
open(f"heroes/{title}.png", "wb").write(png)
At about eight images per minute, even a large backlog clears in minutes, and every image stays on your own hardware.
Tuning steps, resolution and CFG
Three settings control the balance. Steps are the biggest lever: a turbo model is tuned for eight, and pushing higher rarely helps while it always costs time. Resolution is the second lever, since render time scales with pixel count. CFG, the guidance scale, stays low on turbo models — one worked well here — because high guidance over-saturates distilled models. Change one setting at a time and re-generate, so you always know which knob moved the result.
Negative prompts and avoiding artifacts
The second CLIP-text-encode node in the workflow is the negative prompt, and it earns its place. Listing what you do not want — “blurry, low quality, extra fingers, watermark, distorted text” — steers the sampler away from common failure modes. On distilled turbo models the effect is subtler than on full models, but a short, focused negative prompt still trims the worst artifacts.
The text test above showed where artifacts appear: extra, garbled type in areas the prompt implied but did not pin down. A negative prompt that mentions “extra text, gibberish text” reduces those cases. As with everything here, change one thing at a time so you can see what the negative prompt actually bought you.
Frequently asked questions
Does ComfyUI really run on an AMD GPU?
Yes. With a ROCm build of PyTorch, ComfyUI uses the AMD card natively on Windows 11. The server reports it as cuda:0, but the work runs on the Radeon.
Can the model render specific text like a logo or title?
Surprisingly well. In a 10-prompt test it rendered every requested string correctly, and cleanly in seven cases. Keep the text short and ask for a single surface for the best results.
What does the CLIP adherence score mean?
It measures how closely an image matches its prompt. For CLIP, 25–36 already indicates a strong match, and concrete scenes score higher than abstract concepts. Use it to compare prompts, not as a percentage.
How do I automate this for my blog?
Drive the HTTP API from Python: load the workflow JSON, set the prompt and seed, POST to /prompt, poll /history, then download the file from /view. No interaction with the ComfyUI interface is needed.
Why was the first image slow?
The first render loads the model and compiles GPU kernels, a one-time cost of around a minute and a half. Keep the server running and every later image renders in about seven seconds.
Summary
ComfyUI on Windows 11 turns an AMD GPU into a local image factory. Through ROCm and a simple Python API, ERNIE-Image-Turbo rendered blog images in about seven seconds each, with strong prompt adherence (CLIP average 30.9 across ten prompts) and — the real surprise — correct text rendering in all ten of a dedicated text test. The whole pipeline runs offline, costs nothing per image, and automates hero-image generation across a content backlog. Reproduce every result with the scripts: 12_ernie_gallery.py and 12_comfyui_ernie_benchmark.py.
Companion code — every script and benchmark from this series lives in one repository: github.com/bestin-it/qwen-local-ai-scripts.
Free ebook
Free AI Video, Generated Locally
Run Wan 2.1 in ComfyUI on your own GPU — the scripts I use, measured times, sample clips. No cloud, no API keys.
No spam. Unsubscribe at any time.


Leave a Reply