Self-Hosted AI Image Generation: A 45 KB Hero
brew-space, our own unconference platform, runs its first real event Monday. Days out, it still had no launch hero image — and instead of a Midjourney subscription, a stock-photo site, or a day of a designer's time, we reached for self-hosted AI image generation on our own DGX Spark. We wrote a prompt, ran a script against the box, and had a batch of candidates back in about the time it took to write the prompt. That's the whole point of what follows: once the studio stops being a place you click and becomes a capability you script, a launch asset costs you a prompt.
A studio you click is a place; a studio you script is a capability
Months ago we stood the studio up in a day and wrote about the license traps and the real numbers — that build story is here. That post ended at a working ComfyUI you drive by clicking. This one starts where it left off: driving the same ComfyUI headless, over its REST API, from a ~200-line Python script that imports nothing but the standard library. The UI is the right tool for dialing in a look by hand. It's the wrong tool when you want one prompt in, N finished candidates out, without touching a mouse. So we stopped clicking. It's now a bundled internal skill: generate.py, one command.
The whole loop is four HTTP calls
Headless ComfyUI is less mysterious than the node graph makes it look. The generate loop is four calls:
- Introspect —
GET /object_info/<NodeClass>returns the exact model filenames the running server has installed and the exact input keys each node accepts. - Submit —
POST /promptwith the API-format graph; you get back a prompt id. - Poll —
GET /history/{id}until the job reports done. - Download —
GET /viewpulls the finished PNG.
Build the graph as JSON, submit, poll, download. The plumbing is boring on purpose. The interesting part is step one.
Validate the filename; never trust copied JSON
Every ComfyUI tutorial hands you a graph with model filenames baked in. Copy one and it will break — because those filenames are whatever that author had on disk the day they exported it. Checkpoints get renamed, quantized, and re-released; a graph that worked in a blog post six months ago points at a file your box doesn't have. So generate.py never hard-codes a guessed filename. It calls /object_info first, reads back what this machine actually has, and builds the graph against that. Before the hero run, the script confirmed every model file it needed was present before it spent a second generating.
That's the same discipline that keeps you out of license trouble, wearing work clothes. The last post caught "Wan 2.7 open weights" as an SEO fabrication and a banned-for-US model hiding behind a friendly write-up. Same reflex, applied to filenames instead of licenses: verify against the source, don't trust the summary.
A 30-billion-parameter brain that never blinked
The model is Qwen-Image-2512 in fp8 with a 4-step Turbo LoRA — 4 steps, CFG 1.0, a few seconds an image once the weights are warm. Both are commercially licensed; Qwen-Image is Apache 2.0, which matters for a reason we'll get to.
The part we're quietly proud of: nothing else had to stop. Our ~36 GB vLLM "brain" — the 30-to-35B LLM we serve for everything else — stayed resident and kept answering requests the entire time the image lane ran. One environment variable, RESERVE_VRAM=24, walls off enough of the 128 GB unified memory that the image job (~30 GB with weights loaded) never collides with it. No swapping models, no taking a service down. The box was already on and already paid for, so the marginal cost of the hero was zero.
Writing a prompt that leaves room for words
A launch hero isn't a pretty picture; it's a pretty picture with headline text on top. So the prompt has to reserve real estate. We literally ask for the subject on one side and "the left third in deep empty shadow" — and the model composes the negative space for you. The negative prompt does the unglamorous work: ban text, watermarks, logos, and letters, because image models love to invent signage; ban faces and hands, because they're where generations go wrong.
Then you hunt. Turbo steps are cheap, so we fire a batch of seeds, look, and pick — "cheap seeds, expensive winner." Honest caveat: that 4-step Turbo LoRA occasionally duplicates the subject — last time, 2 of 8 seeds came back with a doubled figure. That's not a flaw to hide; it's the reason the cheap-seed sweep exists. You throw those away and keep the one that works.
From a raw PNG to a 45 KB hero
The winner was an overhead, top-down shot of a coffee cup with a rosetta poured into the foam — chosen because it rhymes with brew-space's foam-leaf logo mark without copying it. Off the box it's a raw PNG, about 1.1 MB at 1344x768. One pass through cwebp -q 80 turns that into a 45 KB WebP. It's live at brewspace.dev/hero.webp right now — 45,276 bytes, HTTP 200 — sitting behind a lightweight generative canvas for atmosphere. When we hardened the script today, a fresh 1024x576 candidate came out 683 KB as PNG and 34 KB as WebP.
We didn't stopwatch each brew-space candidate, so we won't pretend we did. For real numbers, the prior post has the measured table: 27.35 seconds for the first image cold, weights included, and roughly 50 seconds for a warm 8-step pass at 1664x928. The hero run was warm 4-step turbo — seconds each, not minutes.
Own it, license it, reproduce it
Here's the part that matters to a business, and it isn't "we have a GPU." The real questions about an AI image are three, and self-hosting a commercial-licensed open model answers all three at once.
Who owns the output. Type a prompt into a hosted tool and ownership is whatever the vendor's terms say this quarter. Generate on hardware you control, from weights you downloaded, and there's no third party in the chain to grant or withhold anything. The brew-space hero is a file on our disk — no terms page to re-read before shipping.
What the license permits. Plenty of hosted tools and even some open weights forbid commercial use, or forbid the US entirely (the prior post catalogs the ones we caught). Qwen-Image is Apache 2.0, checked against the license text, not a listicle — a clean license, in writing, that we could hand a lawyer if anyone ever asked.
Whether you can reproduce it. A hosted model can change under you, and a marketing asset you can't re-render on demand is a liability, not an asset. Self-hosted, a fixed seed re-renders the exact same pixels a year from now. Need the hero at a second size for a social card? Same seed, same weights, same look.
The honest ceiling
Two things we won't oversell. This is not Midjourney-grade instant polish — you dial prompts, you hunt seeds, and taste is still entirely on you. A good designer still does things a model won't, and for some jobs that's the right call. And this only pays off because the box already exists. The studio was a full day to build, and a GB10 isn't free; if you'd have to buy one to make one hero, don't. The math works when the hardware is already earning its keep on other work — which, for us, it was the whole time, serving that 36 GB brain.
Thinking about self-hosting your brand's image generation?
If you're weighing whether self-hosting your brand's image generation makes sense — the licensing, the ownership, whether your team can actually reproduce what it ships — that's exactly what we work through in a free 45-minute AI Opportunity Briefing. Bring the tools you're using now; we'll bring the license texts and the real numbers.